Add ICU message format support
[chromium-blink-merge.git] / extensions / browser / api / app_window / app_window_api.cc
blob2cd0710b7f5d05df23e8d0eeea88f9c3d0f538a1
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_frame_host.h"
15 #include "content/public/browser/render_process_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/common/api/app_window.h"
25 #include "extensions/common/features/simple_feature.h"
26 #include "extensions/common/image_util.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/geometry/rect.h"
33 #include "url/gurl.h"
35 namespace app_window = extensions::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 kImeWindowMissingPermission[] =
61 "Extensions require the \"app.window.ime\" permission to create windows.";
62 const char kImeOptionIsNotSupported[] =
63 "The \"ime\" option is not supported for platform app.";
64 #if !defined(OS_CHROMEOS)
65 const char kImeWindowUnsupportedPlatform[] =
66 "The \"ime\" option can only be used on ChromeOS.";
67 #else
68 const char kImeOptionMustBeTrueAndNeedsFrameNone[] =
69 "IME extensions must create window with \"ime: true\" and "
70 "\"frame: 'none'\".";
71 #endif
72 } // namespace app_window_constants
74 const char kNoneFrameOption[] = "none";
75 // TODO(benwells): Remove HTML titlebar injection.
76 const char kHtmlFrameOption[] = "experimental-html";
78 namespace {
80 // If the same property is specified for the inner and outer bounds, raise an
81 // error.
82 bool CheckBoundsConflict(const scoped_ptr<int>& inner_property,
83 const scoped_ptr<int>& outer_property,
84 const std::string& property_name,
85 std::string* error) {
86 if (inner_property.get() && outer_property.get()) {
87 std::vector<std::string> subst;
88 subst.push_back(property_name);
89 *error = base::ReplaceStringPlaceholders(
90 app_window_constants::kConflictingBoundsOptions, subst, NULL);
91 return false;
94 return true;
97 // Copy over the bounds specification properties from the API to the
98 // AppWindow::CreateParams.
99 void CopyBoundsSpec(const app_window::BoundsSpecification* input_spec,
100 AppWindow::BoundsSpecification* create_spec) {
101 if (!input_spec)
102 return;
104 if (input_spec->left.get())
105 create_spec->bounds.set_x(*input_spec->left);
106 if (input_spec->top.get())
107 create_spec->bounds.set_y(*input_spec->top);
108 if (input_spec->width.get())
109 create_spec->bounds.set_width(*input_spec->width);
110 if (input_spec->height.get())
111 create_spec->bounds.set_height(*input_spec->height);
112 if (input_spec->min_width.get())
113 create_spec->minimum_size.set_width(*input_spec->min_width);
114 if (input_spec->min_height.get())
115 create_spec->minimum_size.set_height(*input_spec->min_height);
116 if (input_spec->max_width.get())
117 create_spec->maximum_size.set_width(*input_spec->max_width);
118 if (input_spec->max_height.get())
119 create_spec->maximum_size.set_height(*input_spec->max_height);
122 } // namespace
124 AppWindowCreateFunction::AppWindowCreateFunction()
125 : inject_html_titlebar_(false) {}
127 bool AppWindowCreateFunction::RunAsync() {
128 // Don't create app window if the system is shutting down.
129 if (ExtensionsBrowserClient::Get()->IsShuttingDown())
130 return false;
132 scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
133 EXTENSION_FUNCTION_VALIDATE(params.get());
135 GURL url = extension()->GetResourceURL(params->url);
136 // Allow absolute URLs for component apps, otherwise prepend the extension
137 // path.
138 // TODO(devlin): Investigate if this is still used. If not, kill it dead!
139 GURL absolute = GURL(params->url);
140 if (absolute.has_scheme()) {
141 if (extension()->location() == Manifest::COMPONENT) {
142 url = absolute;
143 } else {
144 // Show error when url passed isn't local.
145 error_ = app_window_constants::kInvalidUrlParameter;
146 return false;
150 // TODO(jeremya): figure out a way to pass the opening WebContents through to
151 // AppWindow::Create so we can set the opener at create time rather than
152 // with a hack in AppWindowCustomBindings::GetView().
153 AppWindow::CreateParams create_params;
154 app_window::CreateWindowOptions* options = params->options.get();
155 if (options) {
156 if (options->id.get()) {
157 // TODO(mek): use URL if no id specified?
158 // Limit length of id to 256 characters.
159 if (options->id->length() > 256) {
160 error_ = app_window_constants::kInvalidWindowId;
161 return false;
164 create_params.window_key = *options->id;
166 if (options->singleton && *options->singleton == false) {
167 WriteToConsole(
168 content::CONSOLE_MESSAGE_LEVEL_WARNING,
169 "The 'singleton' option in chrome.apps.window.create() is deprecated!"
170 " Change your code to no longer rely on this.");
173 if (!options->singleton || *options->singleton) {
174 AppWindow* existing_window =
175 AppWindowRegistry::Get(browser_context())
176 ->GetAppWindowForAppAndKey(extension_id(),
177 create_params.window_key);
178 if (existing_window) {
179 content::RenderFrameHost* existing_frame =
180 existing_window->web_contents()->GetMainFrame();
181 int frame_id = MSG_ROUTING_NONE;
182 if (render_frame_host()->GetProcess()->GetID() ==
183 existing_frame->GetProcess()->GetID()) {
184 frame_id = existing_frame->GetRoutingID();
187 if (!options->hidden.get() || !*options->hidden.get()) {
188 if (options->focused.get() && !*options->focused.get())
189 existing_window->Show(AppWindow::SHOW_INACTIVE);
190 else
191 existing_window->Show(AppWindow::SHOW_ACTIVE);
194 base::DictionaryValue* result = new base::DictionaryValue;
195 result->Set("frameId", new base::FundamentalValue(frame_id));
196 existing_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 (options->type == app_window::WINDOW_TYPE_PANEL) {
211 #if defined(OS_CHROMEOS)
212 // Panels for v2 apps are only supported on Chrome OS.
213 create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
214 #else
215 WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING,
216 "Panels are not supported on this platform");
217 #endif
220 if (!GetFrameOptions(*options, &create_params))
221 return false;
223 if (extension()->GetType() == Manifest::TYPE_EXTENSION) {
224 // Whitelisted IME extensions are allowed to use this API to create IME
225 // specific windows to show accented characters or suggestions.
226 if (!extension()->permissions_data()->HasAPIPermission(
227 APIPermission::kImeWindowEnabled)) {
228 error_ = app_window_constants::kImeWindowMissingPermission;
229 return false;
232 #if !defined(OS_CHROMEOS)
233 // IME window is only supported on ChromeOS.
234 error_ = app_window_constants::kImeWindowUnsupportedPlatform;
235 return false;
236 #else
237 // IME extensions must create window with "ime: true" and "frame: none".
238 if (!options->ime.get() || !*options->ime.get() ||
239 create_params.frame != AppWindow::FRAME_NONE) {
240 error_ = app_window_constants::kImeOptionMustBeTrueAndNeedsFrameNone;
241 return false;
243 create_params.is_ime_window = true;
244 #endif // OS_CHROMEOS
245 } else {
246 if (options->ime.get()) {
247 error_ = app_window_constants::kImeOptionIsNotSupported;
248 return false;
252 if (options->alpha_enabled.get()) {
253 const char* const kWhitelist[] = {
254 #if defined(OS_CHROMEOS)
255 "B58B99751225318C7EB8CF4688B5434661083E07", // http://crbug.com/410550
256 "06BE211D5F014BAB34BC22D9DDA09C63A81D828E", // http://crbug.com/425539
257 "F94EE6AB36D6C6588670B2B01EB65212D9C64E33",
258 "B9EF10DDFEA11EF77873CC5009809E5037FC4C7A", // http://crbug.com/435380
259 #endif
260 "0F42756099D914A026DADFA182871C015735DD95", // http://crbug.com/323773
261 "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7",
262 "E7E2461CE072DF036CF9592740196159E2D7C089", // http://crbug.com/356200
263 "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3",
264 "312745D9BF916161191143F6490085EEA0434997",
265 "53041A2FA309EECED01FFC751E7399186E860B2C",
266 "A07A5B743CD82A1C2579DB77D353C98A23201EEF", // http://crbug.com/413748
267 "F16F23C83C5F6DAD9B65A120448B34056DD80691",
268 "0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B"
270 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
271 !SimpleFeature::IsIdInArray(
272 extension_id(), kWhitelist, arraysize(kWhitelist))) {
273 error_ = app_window_constants::kAlphaEnabledWrongChannel;
274 return false;
276 if (!extension()->permissions_data()->HasAPIPermission(
277 APIPermission::kAlphaEnabled)) {
278 error_ = app_window_constants::kAlphaEnabledMissingPermission;
279 return false;
281 if (create_params.frame != AppWindow::FRAME_NONE) {
282 error_ = app_window_constants::kAlphaEnabledNeedsFrameNone;
283 return false;
285 #if defined(USE_AURA)
286 create_params.alpha_enabled = *options->alpha_enabled;
287 #else
288 // Transparency is only supported on Aura.
289 // Fallback to creating an opaque window (by ignoring alphaEnabled).
290 #endif
293 if (options->hidden.get())
294 create_params.hidden = *options->hidden.get();
296 if (options->resizable.get())
297 create_params.resizable = *options->resizable.get();
299 if (options->always_on_top.get()) {
300 create_params.always_on_top = *options->always_on_top.get();
302 if (create_params.always_on_top &&
303 !extension()->permissions_data()->HasAPIPermission(
304 APIPermission::kAlwaysOnTopWindows)) {
305 error_ = app_window_constants::kAlwaysOnTopPermission;
306 return false;
310 if (options->focused.get())
311 create_params.focused = *options->focused.get();
313 if (options->visible_on_all_workspaces.get()) {
314 create_params.visible_on_all_workspaces =
315 *options->visible_on_all_workspaces.get();
318 if (options->type != app_window::WINDOW_TYPE_PANEL) {
319 switch (options->state) {
320 case app_window::STATE_NONE:
321 case app_window::STATE_NORMAL:
322 break;
323 case app_window::STATE_FULLSCREEN:
324 create_params.state = ui::SHOW_STATE_FULLSCREEN;
325 break;
326 case app_window::STATE_MAXIMIZED:
327 create_params.state = ui::SHOW_STATE_MAXIMIZED;
328 break;
329 case app_window::STATE_MINIMIZED:
330 create_params.state = ui::SHOW_STATE_MINIMIZED;
331 break;
336 create_params.creator_process_id =
337 render_frame_host()->GetProcess()->GetID();
339 AppWindow* app_window =
340 AppWindowClient::Get()->CreateAppWindow(browser_context(), extension());
341 app_window->Init(url, new AppWindowContentsImpl(app_window), create_params);
343 if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode() &&
344 !app_window->is_ime_window()) {
345 app_window->ForcedFullscreen();
348 content::RenderFrameHost* created_frame =
349 app_window->web_contents()->GetMainFrame();
350 int frame_id = MSG_ROUTING_NONE;
351 if (create_params.creator_process_id == created_frame->GetProcess()->GetID())
352 frame_id = created_frame->GetRoutingID();
354 base::DictionaryValue* result = new base::DictionaryValue;
355 result->Set("frameId", new base::FundamentalValue(frame_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(app_window->web_contents())) {
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 base::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