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_window/app_window_api.h" 6 7 #include "base/command_line.h" 8 #include "base/strings/string_number_conversions.h" 9 #include "base/strings/string_util.h" 10 #include "base/time/time.h" 11 #include "base/values.h" 12 #include "content/public/browser/notification_registrar.h" 13 #include "content/public/browser/notification_types.h" 14 #include "content/public/browser/render_process_host.h" 15 #include "content/public/browser/render_view_host.h" 16 #include "content/public/browser/web_contents.h" 17 #include "content/public/common/url_constants.h" 18 #include "extensions/browser/app_window/app_window.h" 19 #include "extensions/browser/app_window/app_window_client.h" 20 #include "extensions/browser/app_window/app_window_contents.h" 21 #include "extensions/browser/app_window/app_window_registry.h" 22 #include "extensions/browser/app_window/native_app_window.h" 23 #include "extensions/browser/extensions_browser_client.h" 24 #include "extensions/browser/image_util.h" 25 #include "extensions/common/api/app_window.h" 26 #include "extensions/common/features/simple_feature.h" 27 #include "extensions/common/permissions/permissions_data.h" 28 #include "extensions/common/switches.h" 29 #include "third_party/skia/include/core/SkColor.h" 30 #include "ui/base/ui_base_types.h" 31 #include "ui/gfx/rect.h" 32 #include "url/gurl.h" 33 34 namespace app_window = extensions::core_api::app_window; 35 namespace Create = app_window::Create; 36 37 namespace extensions { 38 39 namespace app_window_constants { 40 const char kInvalidWindowId[] = 41 "The window id can not be more than 256 characters long."; 42 const char kInvalidColorSpecification[] = 43 "The color specification could not be parsed."; 44 const char kColorWithFrameNone[] = "Windows with no frame cannot have a color."; 45 const char kInactiveColorWithoutColor[] = 46 "frame.inactiveColor must be used with frame.color."; 47 const char kConflictingBoundsOptions[] = 48 "The $1 property cannot be specified for both inner and outer bounds."; 49 const char kAlwaysOnTopPermission[] = 50 "The \"app.window.alwaysOnTop\" permission is required."; 51 const char kInvalidUrlParameter[] = 52 "The URL used for window creation must be local for security reasons."; 53 const char kAlphaEnabledWrongChannel[] = 54 "The alphaEnabled option requires dev channel or newer."; 55 const char kAlphaEnabledMissingPermission[] = 56 "The alphaEnabled option requires app.window.alpha permission."; 57 const char kAlphaEnabledNeedsFrameNone[] = 58 "The alphaEnabled option can only be used with \"frame: 'none'\"."; 59 const char kVisibleOnAllWorkspacesWrongChannel[] = 60 "The visibleOnAllWorkspaces option requires dev channel or newer."; 61 } // namespace app_window_constants 62 63 const char kNoneFrameOption[] = "none"; 64 // TODO(benwells): Remove HTML titlebar injection. 65 const char kHtmlFrameOption[] = "experimental-html"; 66 67 namespace { 68 69 // If the same property is specified for the inner and outer bounds, raise an 70 // error. 71 bool CheckBoundsConflict(const scoped_ptr<int>& inner_property, 72 const scoped_ptr<int>& outer_property, 73 const std::string& property_name, 74 std::string* error) { 75 if (inner_property.get() && outer_property.get()) { 76 std::vector<std::string> subst; 77 subst.push_back(property_name); 78 *error = ReplaceStringPlaceholders( 79 app_window_constants::kConflictingBoundsOptions, subst, NULL); 80 return false; 81 } 82 83 return true; 84 } 85 86 // Copy over the bounds specification properties from the API to the 87 // AppWindow::CreateParams. 88 void CopyBoundsSpec(const app_window::BoundsSpecification* input_spec, 89 AppWindow::BoundsSpecification* create_spec) { 90 if (!input_spec) 91 return; 92 93 if (input_spec->left.get()) 94 create_spec->bounds.set_x(*input_spec->left); 95 if (input_spec->top.get()) 96 create_spec->bounds.set_y(*input_spec->top); 97 if (input_spec->width.get()) 98 create_spec->bounds.set_width(*input_spec->width); 99 if (input_spec->height.get()) 100 create_spec->bounds.set_height(*input_spec->height); 101 if (input_spec->min_width.get()) 102 create_spec->minimum_size.set_width(*input_spec->min_width); 103 if (input_spec->min_height.get()) 104 create_spec->minimum_size.set_height(*input_spec->min_height); 105 if (input_spec->max_width.get()) 106 create_spec->maximum_size.set_width(*input_spec->max_width); 107 if (input_spec->max_height.get()) 108 create_spec->maximum_size.set_height(*input_spec->max_height); 109 } 110 111 } // namespace 112 113 AppWindowCreateFunction::AppWindowCreateFunction() 114 : inject_html_titlebar_(false) {} 115 116 bool AppWindowCreateFunction::RunAsync() { 117 // Don't create app window if the system is shutting down. 118 if (extensions::ExtensionsBrowserClient::Get()->IsShuttingDown()) 119 return false; 120 121 scoped_ptr<Create::Params> params(Create::Params::Create(*args_)); 122 EXTENSION_FUNCTION_VALIDATE(params.get()); 123 124 GURL url = extension()->GetResourceURL(params->url); 125 // Allow absolute URLs for component apps, otherwise prepend the extension 126 // path. 127 GURL absolute = GURL(params->url); 128 if (absolute.has_scheme()) { 129 if (extension()->location() == extensions::Manifest::COMPONENT) { 130 url = absolute; 131 } else { 132 // Show error when url passed isn't local. 133 error_ = app_window_constants::kInvalidUrlParameter; 134 return false; 135 } 136 } 137 138 // TODO(jeremya): figure out a way to pass the opening WebContents through to 139 // AppWindow::Create so we can set the opener at create time rather than 140 // with a hack in AppWindowCustomBindings::GetView(). 141 AppWindow::CreateParams create_params; 142 app_window::CreateWindowOptions* options = params->options.get(); 143 if (options) { 144 if (options->id.get()) { 145 // TODO(mek): use URL if no id specified? 146 // Limit length of id to 256 characters. 147 if (options->id->length() > 256) { 148 error_ = app_window_constants::kInvalidWindowId; 149 return false; 150 } 151 152 create_params.window_key = *options->id; 153 154 if (options->singleton && *options->singleton == false) { 155 WriteToConsole( 156 content::CONSOLE_MESSAGE_LEVEL_WARNING, 157 "The 'singleton' option in chrome.apps.window.create() is deprecated!" 158 " Change your code to no longer rely on this."); 159 } 160 161 if (!options->singleton || *options->singleton) { 162 AppWindow* window = AppWindowRegistry::Get(browser_context()) 163 ->GetAppWindowForAppAndKey( 164 extension_id(), create_params.window_key); 165 if (window) { 166 content::RenderViewHost* created_view = 167 window->web_contents()->GetRenderViewHost(); 168 int view_id = MSG_ROUTING_NONE; 169 if (render_view_host_->GetProcess()->GetID() == 170 created_view->GetProcess()->GetID()) { 171 view_id = created_view->GetRoutingID(); 172 } 173 174 if (options->hidden.get() && !*options->hidden.get()) { 175 if (options->focused.get() && !*options->focused.get()) 176 window->Show(AppWindow::SHOW_INACTIVE); 177 else 178 window->Show(AppWindow::SHOW_ACTIVE); 179 } 180 181 base::DictionaryValue* result = new base::DictionaryValue; 182 result->Set("viewId", new base::FundamentalValue(view_id)); 183 window->GetSerializedState(result); 184 result->SetBoolean("existingWindow", true); 185 // TODO(benwells): Remove HTML titlebar injection. 186 result->SetBoolean("injectTitlebar", false); 187 SetResult(result); 188 SendResponse(true); 189 return true; 190 } 191 } 192 } 193 194 if (!GetBoundsSpec(*options, &create_params, &error_)) 195 return false; 196 197 if (!AppWindowClient::Get()->IsCurrentChannelOlderThanDev() || 198 extension()->location() == extensions::Manifest::COMPONENT) { 199 if (options->type == app_window::WINDOW_TYPE_PANEL) { 200 create_params.window_type = AppWindow::WINDOW_TYPE_PANEL; 201 } 202 } 203 204 if (!GetFrameOptions(*options, &create_params)) 205 return false; 206 207 if (options->alpha_enabled.get()) { 208 const char* whitelist[] = { 209 #if defined(OS_CHROMEOS) 210 "B58B99751225318C7EB8CF4688B5434661083E07", // http://crbug.com/410550 211 #endif 212 "0F42756099D914A026DADFA182871C015735DD95", // http://crbug.com/323773 213 "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7", 214 "E7E2461CE072DF036CF9592740196159E2D7C089", // http://crbug.com/356200 215 "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3", 216 "312745D9BF916161191143F6490085EEA0434997", 217 "53041A2FA309EECED01FFC751E7399186E860B2C", 218 "A07A5B743CD82A1C2579DB77D353C98A23201EEF", // http://crbug.com/413748 219 "F16F23C83C5F6DAD9B65A120448B34056DD80691", 220 "0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B" 221 }; 222 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() && 223 !extensions::SimpleFeature::IsIdInList( 224 extension_id(), 225 std::set<std::string>(whitelist, 226 whitelist + arraysize(whitelist)))) { 227 error_ = app_window_constants::kAlphaEnabledWrongChannel; 228 return false; 229 } 230 if (!extension()->permissions_data()->HasAPIPermission( 231 APIPermission::kAlphaEnabled)) { 232 error_ = app_window_constants::kAlphaEnabledMissingPermission; 233 return false; 234 } 235 if (create_params.frame != AppWindow::FRAME_NONE) { 236 error_ = app_window_constants::kAlphaEnabledNeedsFrameNone; 237 return false; 238 } 239 #if defined(USE_AURA) 240 create_params.alpha_enabled = *options->alpha_enabled; 241 #else 242 // Transparency is only supported on Aura. 243 // Fallback to creating an opaque window (by ignoring alphaEnabled). 244 #endif 245 } 246 247 if (options->hidden.get()) 248 create_params.hidden = *options->hidden.get(); 249 250 if (options->resizable.get()) 251 create_params.resizable = *options->resizable.get(); 252 253 if (options->always_on_top.get()) { 254 create_params.always_on_top = *options->always_on_top.get(); 255 256 if (create_params.always_on_top && 257 !extension()->permissions_data()->HasAPIPermission( 258 APIPermission::kAlwaysOnTopWindows)) { 259 error_ = app_window_constants::kAlwaysOnTopPermission; 260 return false; 261 } 262 } 263 264 if (options->focused.get()) 265 create_params.focused = *options->focused.get(); 266 267 if (options->visible_on_all_workspaces.get()) { 268 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) { 269 error_ = app_window_constants::kVisibleOnAllWorkspacesWrongChannel; 270 return false; 271 } 272 create_params.visible_on_all_workspaces = 273 *options->visible_on_all_workspaces.get(); 274 } 275 276 if (options->type != app_window::WINDOW_TYPE_PANEL) { 277 switch (options->state) { 278 case app_window::STATE_NONE: 279 case app_window::STATE_NORMAL: 280 break; 281 case app_window::STATE_FULLSCREEN: 282 create_params.state = ui::SHOW_STATE_FULLSCREEN; 283 break; 284 case app_window::STATE_MAXIMIZED: 285 create_params.state = ui::SHOW_STATE_MAXIMIZED; 286 break; 287 case app_window::STATE_MINIMIZED: 288 create_params.state = ui::SHOW_STATE_MINIMIZED; 289 break; 290 } 291 } 292 } 293 294 create_params.creator_process_id = 295 render_view_host_->GetProcess()->GetID(); 296 297 AppWindow* app_window = 298 AppWindowClient::Get()->CreateAppWindow(browser_context(), extension()); 299 app_window->Init(url, new AppWindowContentsImpl(app_window), create_params); 300 301 if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode()) 302 app_window->ForcedFullscreen(); 303 304 content::RenderViewHost* created_view = 305 app_window->web_contents()->GetRenderViewHost(); 306 int view_id = MSG_ROUTING_NONE; 307 if (create_params.creator_process_id == created_view->GetProcess()->GetID()) 308 view_id = created_view->GetRoutingID(); 309 310 base::DictionaryValue* result = new base::DictionaryValue; 311 result->Set("viewId", new base::FundamentalValue(view_id)); 312 result->Set("injectTitlebar", 313 new base::FundamentalValue(inject_html_titlebar_)); 314 result->Set("id", new base::StringValue(app_window->window_key())); 315 app_window->GetSerializedState(result); 316 SetResult(result); 317 318 if (AppWindowRegistry::Get(browser_context()) 319 ->HadDevToolsAttached(created_view)) { 320 AppWindowClient::Get()->OpenDevToolsWindow( 321 app_window->web_contents(), 322 base::Bind(&AppWindowCreateFunction::SendResponse, this, true)); 323 return true; 324 } 325 326 SendResponse(true); 327 app_window->WindowEventsReady(); 328 329 return true; 330 } 331 332 bool AppWindowCreateFunction::GetBoundsSpec( 333 const app_window::CreateWindowOptions& options, 334 AppWindow::CreateParams* params, 335 std::string* error) { 336 DCHECK(params); 337 DCHECK(error); 338 339 if (options.inner_bounds.get() || options.outer_bounds.get()) { 340 // Parse the inner and outer bounds specifications. If developers use the 341 // new API, the deprecated fields will be ignored - do not attempt to merge 342 // them. 343 344 const app_window::BoundsSpecification* inner_bounds = 345 options.inner_bounds.get(); 346 const app_window::BoundsSpecification* outer_bounds = 347 options.outer_bounds.get(); 348 if (inner_bounds && outer_bounds) { 349 if (!CheckBoundsConflict( 350 inner_bounds->left, outer_bounds->left, "left", error)) { 351 return false; 352 } 353 if (!CheckBoundsConflict( 354 inner_bounds->top, outer_bounds->top, "top", error)) { 355 return false; 356 } 357 if (!CheckBoundsConflict( 358 inner_bounds->width, outer_bounds->width, "width", error)) { 359 return false; 360 } 361 if (!CheckBoundsConflict( 362 inner_bounds->height, outer_bounds->height, "height", error)) { 363 return false; 364 } 365 if (!CheckBoundsConflict(inner_bounds->min_width, 366 outer_bounds->min_width, 367 "minWidth", 368 error)) { 369 return false; 370 } 371 if (!CheckBoundsConflict(inner_bounds->min_height, 372 outer_bounds->min_height, 373 "minHeight", 374 error)) { 375 return false; 376 } 377 if (!CheckBoundsConflict(inner_bounds->max_width, 378 outer_bounds->max_width, 379 "maxWidth", 380 error)) { 381 return false; 382 } 383 if (!CheckBoundsConflict(inner_bounds->max_height, 384 outer_bounds->max_height, 385 "maxHeight", 386 error)) { 387 return false; 388 } 389 } 390 391 CopyBoundsSpec(inner_bounds, &(params->content_spec)); 392 CopyBoundsSpec(outer_bounds, &(params->window_spec)); 393 } else { 394 // Parse deprecated fields. 395 // Due to a bug in NativeAppWindow::GetFrameInsets() on Windows and ChromeOS 396 // the bounds set the position of the window and the size of the content. 397 // This will be preserved as apps may be relying on this behavior. 398 399 if (options.default_width.get()) 400 params->content_spec.bounds.set_width(*options.default_width.get()); 401 if (options.default_height.get()) 402 params->content_spec.bounds.set_height(*options.default_height.get()); 403 if (options.default_left.get()) 404 params->window_spec.bounds.set_x(*options.default_left.get()); 405 if (options.default_top.get()) 406 params->window_spec.bounds.set_y(*options.default_top.get()); 407 408 if (options.width.get()) 409 params->content_spec.bounds.set_width(*options.width.get()); 410 if (options.height.get()) 411 params->content_spec.bounds.set_height(*options.height.get()); 412 if (options.left.get()) 413 params->window_spec.bounds.set_x(*options.left.get()); 414 if (options.top.get()) 415 params->window_spec.bounds.set_y(*options.top.get()); 416 417 if (options.bounds.get()) { 418 app_window::ContentBounds* bounds = options.bounds.get(); 419 if (bounds->width.get()) 420 params->content_spec.bounds.set_width(*bounds->width.get()); 421 if (bounds->height.get()) 422 params->content_spec.bounds.set_height(*bounds->height.get()); 423 if (bounds->left.get()) 424 params->window_spec.bounds.set_x(*bounds->left.get()); 425 if (bounds->top.get()) 426 params->window_spec.bounds.set_y(*bounds->top.get()); 427 } 428 429 gfx::Size& minimum_size = params->content_spec.minimum_size; 430 if (options.min_width.get()) 431 minimum_size.set_width(*options.min_width); 432 if (options.min_height.get()) 433 minimum_size.set_height(*options.min_height); 434 gfx::Size& maximum_size = params->content_spec.maximum_size; 435 if (options.max_width.get()) 436 maximum_size.set_width(*options.max_width); 437 if (options.max_height.get()) 438 maximum_size.set_height(*options.max_height); 439 } 440 441 return true; 442 } 443 444 AppWindow::Frame AppWindowCreateFunction::GetFrameFromString( 445 const std::string& frame_string) { 446 if (frame_string == kHtmlFrameOption && 447 (extension()->permissions_data()->HasAPIPermission( 448 APIPermission::kExperimental) || 449 CommandLine::ForCurrentProcess()->HasSwitch( 450 switches::kEnableExperimentalExtensionApis))) { 451 inject_html_titlebar_ = true; 452 return AppWindow::FRAME_NONE; 453 } 454 455 if (frame_string == kNoneFrameOption) 456 return AppWindow::FRAME_NONE; 457 458 return AppWindow::FRAME_CHROME; 459 } 460 461 bool AppWindowCreateFunction::GetFrameOptions( 462 const app_window::CreateWindowOptions& options, 463 AppWindow::CreateParams* create_params) { 464 if (!options.frame) 465 return true; 466 467 DCHECK(options.frame->as_string || options.frame->as_frame_options); 468 if (options.frame->as_string) { 469 create_params->frame = GetFrameFromString(*options.frame->as_string); 470 return true; 471 } 472 473 if (options.frame->as_frame_options->type) 474 create_params->frame = 475 GetFrameFromString(*options.frame->as_frame_options->type); 476 477 if (options.frame->as_frame_options->color.get()) { 478 if (create_params->frame != AppWindow::FRAME_CHROME) { 479 error_ = app_window_constants::kColorWithFrameNone; 480 return false; 481 } 482 483 if (!image_util::ParseCSSColorString( 484 *options.frame->as_frame_options->color, 485 &create_params->active_frame_color)) { 486 error_ = app_window_constants::kInvalidColorSpecification; 487 return false; 488 } 489 490 create_params->has_frame_color = true; 491 create_params->inactive_frame_color = create_params->active_frame_color; 492 493 if (options.frame->as_frame_options->inactive_color.get()) { 494 if (!image_util::ParseCSSColorString( 495 *options.frame->as_frame_options->inactive_color, 496 &create_params->inactive_frame_color)) { 497 error_ = app_window_constants::kInvalidColorSpecification; 498 return false; 499 } 500 } 501 502 return true; 503 } 504 505 if (options.frame->as_frame_options->inactive_color.get()) { 506 error_ = app_window_constants::kInactiveColorWithoutColor; 507 return false; 508 } 509 510 return true; 511 } 512 513 } // namespace extensions 514