Add testing/scripts/OWNERS
[chromium-blink-merge.git] / extensions / browser / api / app_window / app_window_api.cc
blobd0e52f2b1675338434595ecce208b582e0b53ddb
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.
5 #include "extensions/browser/api/app_window/app_window_api.h"
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/manifest.h"
28 #include "extensions/common/permissions/permissions_data.h"
29 #include "extensions/common/switches.h"
30 #include "third_party/skia/include/core/SkColor.h"
31 #include "ui/base/ui_base_types.h"
32 #include "ui/gfx/rect.h"
33 #include "url/gurl.h"
35 namespace app_window = extensions::core_api::app_window;
36 namespace Create = app_window::Create;
38 namespace extensions {
40 namespace app_window_constants {
41 const char kInvalidWindowId[] =
42 "The window id can not be more than 256 characters long.";
43 const char kInvalidColorSpecification[] =
44 "The color specification could not be parsed.";
45 const char kColorWithFrameNone[] = "Windows with no frame cannot have a color.";
46 const char kInactiveColorWithoutColor[] =
47 "frame.inactiveColor must be used with frame.color.";
48 const char kConflictingBoundsOptions[] =
49 "The $1 property cannot be specified for both inner and outer bounds.";
50 const char kAlwaysOnTopPermission[] =
51 "The \"app.window.alwaysOnTop\" permission is required.";
52 const char kInvalidUrlParameter[] =
53 "The URL used for window creation must be local for security reasons.";
54 const char kAlphaEnabledWrongChannel[] =
55 "The alphaEnabled option requires dev channel or newer.";
56 const char kAlphaEnabledMissingPermission[] =
57 "The alphaEnabled option requires app.window.alpha permission.";
58 const char kAlphaEnabledNeedsFrameNone[] =
59 "The alphaEnabled option can only be used with \"frame: 'none'\".";
60 const char kVisibleOnAllWorkspacesWrongChannel[] =
61 "The visibleOnAllWorkspaces option requires dev channel or newer.";
62 const char kImeWindowMissingPermission[] =
63 "Extensions require the \"app.window.ime\" permission to create windows.";
64 const char kImeOptionIsNotSupported[] =
65 "The \"ime\" option is not supported for platform app.";
66 #if !defined(OS_CHROMEOS)
67 const char kImeWindowUnsupportedPlatform[] =
68 "The \"ime\" option can only be used on ChromeOS.";
69 #else
70 const char kImeOptionMustBeTrueAndNeedsFrameNone[] =
71 "IME extensions must create window with \"ime: true\" and "
72 "\"frame: 'none'\".";
73 #endif
74 } // namespace app_window_constants
76 const char kNoneFrameOption[] = "none";
77 // TODO(benwells): Remove HTML titlebar injection.
78 const char kHtmlFrameOption[] = "experimental-html";
80 namespace {
82 // If the same property is specified for the inner and outer bounds, raise an
83 // error.
84 bool CheckBoundsConflict(const scoped_ptr<int>& inner_property,
85 const scoped_ptr<int>& outer_property,
86 const std::string& property_name,
87 std::string* error) {
88 if (inner_property.get() && outer_property.get()) {
89 std::vector<std::string> subst;
90 subst.push_back(property_name);
91 *error = ReplaceStringPlaceholders(
92 app_window_constants::kConflictingBoundsOptions, subst, NULL);
93 return false;
96 return true;
99 // Copy over the bounds specification properties from the API to the
100 // AppWindow::CreateParams.
101 void CopyBoundsSpec(const app_window::BoundsSpecification* input_spec,
102 AppWindow::BoundsSpecification* create_spec) {
103 if (!input_spec)
104 return;
106 if (input_spec->left.get())
107 create_spec->bounds.set_x(*input_spec->left);
108 if (input_spec->top.get())
109 create_spec->bounds.set_y(*input_spec->top);
110 if (input_spec->width.get())
111 create_spec->bounds.set_width(*input_spec->width);
112 if (input_spec->height.get())
113 create_spec->bounds.set_height(*input_spec->height);
114 if (input_spec->min_width.get())
115 create_spec->minimum_size.set_width(*input_spec->min_width);
116 if (input_spec->min_height.get())
117 create_spec->minimum_size.set_height(*input_spec->min_height);
118 if (input_spec->max_width.get())
119 create_spec->maximum_size.set_width(*input_spec->max_width);
120 if (input_spec->max_height.get())
121 create_spec->maximum_size.set_height(*input_spec->max_height);
124 } // namespace
126 AppWindowCreateFunction::AppWindowCreateFunction()
127 : inject_html_titlebar_(false) {}
129 bool AppWindowCreateFunction::RunAsync() {
130 // Don't create app window if the system is shutting down.
131 if (extensions::ExtensionsBrowserClient::Get()->IsShuttingDown())
132 return false;
134 scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
135 EXTENSION_FUNCTION_VALIDATE(params.get());
137 GURL url = extension()->GetResourceURL(params->url);
138 // Allow absolute URLs for component apps, otherwise prepend the extension
139 // path.
140 GURL absolute = GURL(params->url);
141 if (absolute.has_scheme()) {
142 if (extension()->location() == extensions::Manifest::COMPONENT) {
143 url = absolute;
144 } else {
145 // Show error when url passed isn't local.
146 error_ = app_window_constants::kInvalidUrlParameter;
147 return false;
151 // TODO(jeremya): figure out a way to pass the opening WebContents through to
152 // AppWindow::Create so we can set the opener at create time rather than
153 // with a hack in AppWindowCustomBindings::GetView().
154 AppWindow::CreateParams create_params;
155 app_window::CreateWindowOptions* options = params->options.get();
156 if (options) {
157 if (options->id.get()) {
158 // TODO(mek): use URL if no id specified?
159 // Limit length of id to 256 characters.
160 if (options->id->length() > 256) {
161 error_ = app_window_constants::kInvalidWindowId;
162 return false;
165 create_params.window_key = *options->id;
167 if (options->singleton && *options->singleton == false) {
168 WriteToConsole(
169 content::CONSOLE_MESSAGE_LEVEL_WARNING,
170 "The 'singleton' option in chrome.apps.window.create() is deprecated!"
171 " Change your code to no longer rely on this.");
174 if (!options->singleton || *options->singleton) {
175 AppWindow* window = AppWindowRegistry::Get(browser_context())
176 ->GetAppWindowForAppAndKey(
177 extension_id(), create_params.window_key);
178 if (window) {
179 content::RenderViewHost* created_view =
180 window->web_contents()->GetRenderViewHost();
181 int view_id = MSG_ROUTING_NONE;
182 if (render_view_host_->GetProcess()->GetID() ==
183 created_view->GetProcess()->GetID()) {
184 view_id = created_view->GetRoutingID();
187 if (options->hidden.get() && !*options->hidden.get()) {
188 if (options->focused.get() && !*options->focused.get())
189 window->Show(AppWindow::SHOW_INACTIVE);
190 else
191 window->Show(AppWindow::SHOW_ACTIVE);
194 base::DictionaryValue* result = new base::DictionaryValue;
195 result->Set("viewId", new base::FundamentalValue(view_id));
196 window->GetSerializedState(result);
197 result->SetBoolean("existingWindow", true);
198 // TODO(benwells): Remove HTML titlebar injection.
199 result->SetBoolean("injectTitlebar", false);
200 SetResult(result);
201 SendResponse(true);
202 return true;
207 if (!GetBoundsSpec(*options, &create_params, &error_))
208 return false;
210 if (!AppWindowClient::Get()->IsCurrentChannelOlderThanDev() ||
211 extension()->location() == extensions::Manifest::COMPONENT) {
212 if (options->type == app_window::WINDOW_TYPE_PANEL) {
213 create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
217 if (!GetFrameOptions(*options, &create_params))
218 return false;
220 if (extension()->GetType() == extensions::Manifest::TYPE_EXTENSION) {
221 // Whitelisted IME extensions are allowed to use this API to create IME
222 // specific windows to show accented characters or suggestions.
223 if (!extension()->permissions_data()->HasAPIPermission(
224 APIPermission::kImeWindowEnabled)) {
225 error_ = app_window_constants::kImeWindowMissingPermission;
226 return false;
229 #if !defined(OS_CHROMEOS)
230 // IME window is only supported on ChromeOS.
231 error_ = app_window_constants::kImeWindowUnsupportedPlatform;
232 return false;
233 #else
234 // IME extensions must create window with "ime: true" and "frame: none".
235 if (!options->ime.get() || !*options->ime.get() ||
236 create_params.frame != AppWindow::FRAME_NONE) {
237 error_ = app_window_constants::kImeOptionMustBeTrueAndNeedsFrameNone;
238 return false;
240 create_params.is_ime_window = true;
241 #endif // OS_CHROMEOS
242 } else {
243 if (options->ime.get()) {
244 error_ = app_window_constants::kImeOptionIsNotSupported;
245 return false;
249 if (options->alpha_enabled.get()) {
250 const char* whitelist[] = {
251 #if defined(OS_CHROMEOS)
252 "B58B99751225318C7EB8CF4688B5434661083E07", // http://crbug.com/410550
253 "06BE211D5F014BAB34BC22D9DDA09C63A81D828E", // http://crbug.com/425539
254 "F94EE6AB36D6C6588670B2B01EB65212D9C64E33",
255 #endif
256 "0F42756099D914A026DADFA182871C015735DD95", // http://crbug.com/323773
257 "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7",
258 "E7E2461CE072DF036CF9592740196159E2D7C089", // http://crbug.com/356200
259 "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3",
260 "312745D9BF916161191143F6490085EEA0434997",
261 "53041A2FA309EECED01FFC751E7399186E860B2C",
262 "A07A5B743CD82A1C2579DB77D353C98A23201EEF", // http://crbug.com/413748
263 "F16F23C83C5F6DAD9B65A120448B34056DD80691",
264 "0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B"
266 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
267 !extensions::SimpleFeature::IsIdInList(
268 extension_id(),
269 std::set<std::string>(whitelist,
270 whitelist + arraysize(whitelist)))) {
271 error_ = app_window_constants::kAlphaEnabledWrongChannel;
272 return false;
274 if (!extension()->permissions_data()->HasAPIPermission(
275 APIPermission::kAlphaEnabled)) {
276 error_ = app_window_constants::kAlphaEnabledMissingPermission;
277 return false;
279 if (create_params.frame != AppWindow::FRAME_NONE) {
280 error_ = app_window_constants::kAlphaEnabledNeedsFrameNone;
281 return false;
283 #if defined(USE_AURA)
284 create_params.alpha_enabled = *options->alpha_enabled;
285 #else
286 // Transparency is only supported on Aura.
287 // Fallback to creating an opaque window (by ignoring alphaEnabled).
288 #endif
291 if (options->hidden.get())
292 create_params.hidden = *options->hidden.get();
294 if (options->resizable.get())
295 create_params.resizable = *options->resizable.get();
297 if (options->always_on_top.get()) {
298 create_params.always_on_top = *options->always_on_top.get();
300 if (create_params.always_on_top &&
301 !extension()->permissions_data()->HasAPIPermission(
302 APIPermission::kAlwaysOnTopWindows)) {
303 error_ = app_window_constants::kAlwaysOnTopPermission;
304 return false;
308 if (options->focused.get())
309 create_params.focused = *options->focused.get();
311 if (options->visible_on_all_workspaces.get()) {
312 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) {
313 error_ = app_window_constants::kVisibleOnAllWorkspacesWrongChannel;
314 return false;
316 create_params.visible_on_all_workspaces =
317 *options->visible_on_all_workspaces.get();
320 if (options->type != app_window::WINDOW_TYPE_PANEL) {
321 switch (options->state) {
322 case app_window::STATE_NONE:
323 case app_window::STATE_NORMAL:
324 break;
325 case app_window::STATE_FULLSCREEN:
326 create_params.state = ui::SHOW_STATE_FULLSCREEN;
327 break;
328 case app_window::STATE_MAXIMIZED:
329 create_params.state = ui::SHOW_STATE_MAXIMIZED;
330 break;
331 case app_window::STATE_MINIMIZED:
332 create_params.state = ui::SHOW_STATE_MINIMIZED;
333 break;
338 create_params.creator_process_id =
339 render_view_host_->GetProcess()->GetID();
341 AppWindow* app_window =
342 AppWindowClient::Get()->CreateAppWindow(browser_context(), extension());
343 app_window->Init(url, new AppWindowContentsImpl(app_window), create_params);
345 if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode())
346 app_window->ForcedFullscreen();
348 content::RenderViewHost* created_view =
349 app_window->web_contents()->GetRenderViewHost();
350 int view_id = MSG_ROUTING_NONE;
351 if (create_params.creator_process_id == created_view->GetProcess()->GetID())
352 view_id = created_view->GetRoutingID();
354 base::DictionaryValue* result = new base::DictionaryValue;
355 result->Set("viewId", new base::FundamentalValue(view_id));
356 result->Set("injectTitlebar",
357 new base::FundamentalValue(inject_html_titlebar_));
358 result->Set("id", new base::StringValue(app_window->window_key()));
359 app_window->GetSerializedState(result);
360 SetResult(result);
362 if (AppWindowRegistry::Get(browser_context())
363 ->HadDevToolsAttached(created_view)) {
364 AppWindowClient::Get()->OpenDevToolsWindow(
365 app_window->web_contents(),
366 base::Bind(&AppWindowCreateFunction::SendResponse, this, true));
367 return true;
370 SendResponse(true);
371 app_window->WindowEventsReady();
373 return true;
376 bool AppWindowCreateFunction::GetBoundsSpec(
377 const app_window::CreateWindowOptions& options,
378 AppWindow::CreateParams* params,
379 std::string* error) {
380 DCHECK(params);
381 DCHECK(error);
383 if (options.inner_bounds.get() || options.outer_bounds.get()) {
384 // Parse the inner and outer bounds specifications. If developers use the
385 // new API, the deprecated fields will be ignored - do not attempt to merge
386 // them.
388 const app_window::BoundsSpecification* inner_bounds =
389 options.inner_bounds.get();
390 const app_window::BoundsSpecification* outer_bounds =
391 options.outer_bounds.get();
392 if (inner_bounds && outer_bounds) {
393 if (!CheckBoundsConflict(
394 inner_bounds->left, outer_bounds->left, "left", error)) {
395 return false;
397 if (!CheckBoundsConflict(
398 inner_bounds->top, outer_bounds->top, "top", error)) {
399 return false;
401 if (!CheckBoundsConflict(
402 inner_bounds->width, outer_bounds->width, "width", error)) {
403 return false;
405 if (!CheckBoundsConflict(
406 inner_bounds->height, outer_bounds->height, "height", error)) {
407 return false;
409 if (!CheckBoundsConflict(inner_bounds->min_width,
410 outer_bounds->min_width,
411 "minWidth",
412 error)) {
413 return false;
415 if (!CheckBoundsConflict(inner_bounds->min_height,
416 outer_bounds->min_height,
417 "minHeight",
418 error)) {
419 return false;
421 if (!CheckBoundsConflict(inner_bounds->max_width,
422 outer_bounds->max_width,
423 "maxWidth",
424 error)) {
425 return false;
427 if (!CheckBoundsConflict(inner_bounds->max_height,
428 outer_bounds->max_height,
429 "maxHeight",
430 error)) {
431 return false;
435 CopyBoundsSpec(inner_bounds, &(params->content_spec));
436 CopyBoundsSpec(outer_bounds, &(params->window_spec));
437 } else {
438 // Parse deprecated fields.
439 // Due to a bug in NativeAppWindow::GetFrameInsets() on Windows and ChromeOS
440 // the bounds set the position of the window and the size of the content.
441 // This will be preserved as apps may be relying on this behavior.
443 if (options.default_width.get())
444 params->content_spec.bounds.set_width(*options.default_width.get());
445 if (options.default_height.get())
446 params->content_spec.bounds.set_height(*options.default_height.get());
447 if (options.default_left.get())
448 params->window_spec.bounds.set_x(*options.default_left.get());
449 if (options.default_top.get())
450 params->window_spec.bounds.set_y(*options.default_top.get());
452 if (options.width.get())
453 params->content_spec.bounds.set_width(*options.width.get());
454 if (options.height.get())
455 params->content_spec.bounds.set_height(*options.height.get());
456 if (options.left.get())
457 params->window_spec.bounds.set_x(*options.left.get());
458 if (options.top.get())
459 params->window_spec.bounds.set_y(*options.top.get());
461 if (options.bounds.get()) {
462 app_window::ContentBounds* bounds = options.bounds.get();
463 if (bounds->width.get())
464 params->content_spec.bounds.set_width(*bounds->width.get());
465 if (bounds->height.get())
466 params->content_spec.bounds.set_height(*bounds->height.get());
467 if (bounds->left.get())
468 params->window_spec.bounds.set_x(*bounds->left.get());
469 if (bounds->top.get())
470 params->window_spec.bounds.set_y(*bounds->top.get());
473 gfx::Size& minimum_size = params->content_spec.minimum_size;
474 if (options.min_width.get())
475 minimum_size.set_width(*options.min_width);
476 if (options.min_height.get())
477 minimum_size.set_height(*options.min_height);
478 gfx::Size& maximum_size = params->content_spec.maximum_size;
479 if (options.max_width.get())
480 maximum_size.set_width(*options.max_width);
481 if (options.max_height.get())
482 maximum_size.set_height(*options.max_height);
485 return true;
488 AppWindow::Frame AppWindowCreateFunction::GetFrameFromString(
489 const std::string& frame_string) {
490 if (frame_string == kHtmlFrameOption &&
491 (extension()->permissions_data()->HasAPIPermission(
492 APIPermission::kExperimental) ||
493 CommandLine::ForCurrentProcess()->HasSwitch(
494 switches::kEnableExperimentalExtensionApis))) {
495 inject_html_titlebar_ = true;
496 return AppWindow::FRAME_NONE;
499 if (frame_string == kNoneFrameOption)
500 return AppWindow::FRAME_NONE;
502 return AppWindow::FRAME_CHROME;
505 bool AppWindowCreateFunction::GetFrameOptions(
506 const app_window::CreateWindowOptions& options,
507 AppWindow::CreateParams* create_params) {
508 if (!options.frame)
509 return true;
511 DCHECK(options.frame->as_string || options.frame->as_frame_options);
512 if (options.frame->as_string) {
513 create_params->frame = GetFrameFromString(*options.frame->as_string);
514 return true;
517 if (options.frame->as_frame_options->type)
518 create_params->frame =
519 GetFrameFromString(*options.frame->as_frame_options->type);
521 if (options.frame->as_frame_options->color.get()) {
522 if (create_params->frame != AppWindow::FRAME_CHROME) {
523 error_ = app_window_constants::kColorWithFrameNone;
524 return false;
527 if (!image_util::ParseCSSColorString(
528 *options.frame->as_frame_options->color,
529 &create_params->active_frame_color)) {
530 error_ = app_window_constants::kInvalidColorSpecification;
531 return false;
534 create_params->has_frame_color = true;
535 create_params->inactive_frame_color = create_params->active_frame_color;
537 if (options.frame->as_frame_options->inactive_color.get()) {
538 if (!image_util::ParseCSSColorString(
539 *options.frame->as_frame_options->inactive_color,
540 &create_params->inactive_frame_color)) {
541 error_ = app_window_constants::kInvalidColorSpecification;
542 return false;
546 return true;
549 if (options.frame->as_frame_options->inactive_color.get()) {
550 error_ = app_window_constants::kInactiveColorWithoutColor;
551 return false;
554 return true;
557 } // namespace extensions