Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / extensions / browser / api / app_window / app_window_api.cc
blob71241923ec8527fe51d6b8d02f165129f9b0eebb
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/content_switches.h"
18 #include "content/public/common/url_constants.h"
19 #include "extensions/browser/app_window/app_window.h"
20 #include "extensions/browser/app_window/app_window_client.h"
21 #include "extensions/browser/app_window/app_window_contents.h"
22 #include "extensions/browser/app_window/app_window_registry.h"
23 #include "extensions/browser/app_window/native_app_window.h"
24 #include "extensions/browser/extensions_browser_client.h"
25 #include "extensions/common/api/app_window.h"
26 #include "extensions/common/features/simple_feature.h"
27 #include "extensions/common/image_util.h"
28 #include "extensions/common/manifest.h"
29 #include "extensions/common/permissions/permissions_data.h"
30 #include "extensions/common/switches.h"
31 #include "third_party/skia/include/core/SkColor.h"
32 #include "ui/base/ui_base_types.h"
33 #include "ui/gfx/geometry/rect.h"
34 #include "url/gurl.h"
36 namespace app_window = extensions::api::app_window;
37 namespace Create = app_window::Create;
39 namespace extensions {
41 namespace app_window_constants {
42 const char kInvalidWindowId[] =
43 "The window id can not be more than 256 characters long.";
44 const char kInvalidColorSpecification[] =
45 "The color specification could not be parsed.";
46 const char kColorWithFrameNone[] = "Windows with no frame cannot have a color.";
47 const char kInactiveColorWithoutColor[] =
48 "frame.inactiveColor must be used with frame.color.";
49 const char kConflictingBoundsOptions[] =
50 "The $1 property cannot be specified for both inner and outer bounds.";
51 const char kAlwaysOnTopPermission[] =
52 "The \"app.window.alwaysOnTop\" permission is required.";
53 const char kInvalidUrlParameter[] =
54 "The URL used for window creation must be local for security reasons.";
55 const char kAlphaEnabledWrongChannel[] =
56 "The alphaEnabled option requires dev channel or newer.";
57 const char kAlphaEnabledMissingPermission[] =
58 "The alphaEnabled option requires app.window.alpha permission.";
59 const char kAlphaEnabledNeedsFrameNone[] =
60 "The alphaEnabled option can only be used with \"frame: 'none'\".";
61 const char kImeWindowMissingPermission[] =
62 "Extensions require the \"app.window.ime\" permission to create windows.";
63 const char kImeOptionIsNotSupported[] =
64 "The \"ime\" option is not supported for platform app.";
65 #if !defined(OS_CHROMEOS)
66 const char kImeWindowUnsupportedPlatform[] =
67 "The \"ime\" option can only be used on ChromeOS.";
68 #else
69 const char kImeOptionMustBeTrueAndNeedsFrameNone[] =
70 "IME extensions must create window with \"ime: true\" and "
71 "\"frame: 'none'\".";
72 #endif
73 } // namespace app_window_constants
75 const char kNoneFrameOption[] = "none";
76 // TODO(benwells): Remove HTML titlebar injection.
77 const char kHtmlFrameOption[] = "experimental-html";
79 namespace {
81 // If the same property is specified for the inner and outer bounds, raise an
82 // error.
83 bool CheckBoundsConflict(const scoped_ptr<int>& inner_property,
84 const scoped_ptr<int>& outer_property,
85 const std::string& property_name,
86 std::string* error) {
87 if (inner_property.get() && outer_property.get()) {
88 std::vector<std::string> subst;
89 subst.push_back(property_name);
90 *error = base::ReplaceStringPlaceholders(
91 app_window_constants::kConflictingBoundsOptions, subst, NULL);
92 return false;
95 return true;
98 // Copy over the bounds specification properties from the API to the
99 // AppWindow::CreateParams.
100 void CopyBoundsSpec(const app_window::BoundsSpecification* input_spec,
101 AppWindow::BoundsSpecification* create_spec) {
102 if (!input_spec)
103 return;
105 if (input_spec->left.get())
106 create_spec->bounds.set_x(*input_spec->left);
107 if (input_spec->top.get())
108 create_spec->bounds.set_y(*input_spec->top);
109 if (input_spec->width.get())
110 create_spec->bounds.set_width(*input_spec->width);
111 if (input_spec->height.get())
112 create_spec->bounds.set_height(*input_spec->height);
113 if (input_spec->min_width.get())
114 create_spec->minimum_size.set_width(*input_spec->min_width);
115 if (input_spec->min_height.get())
116 create_spec->minimum_size.set_height(*input_spec->min_height);
117 if (input_spec->max_width.get())
118 create_spec->maximum_size.set_width(*input_spec->max_width);
119 if (input_spec->max_height.get())
120 create_spec->maximum_size.set_height(*input_spec->max_height);
123 } // namespace
125 AppWindowCreateFunction::AppWindowCreateFunction()
126 : inject_html_titlebar_(false) {}
128 bool AppWindowCreateFunction::RunAsync() {
129 // Don't create app window if the system is shutting down.
130 if (ExtensionsBrowserClient::Get()->IsShuttingDown())
131 return false;
133 scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
134 EXTENSION_FUNCTION_VALIDATE(params.get());
136 GURL url = extension()->GetResourceURL(params->url);
137 // Allow absolute URLs for component apps, otherwise prepend the extension
138 // path.
139 // TODO(devlin): Investigate if this is still used. If not, kill it dead!
140 GURL absolute = GURL(params->url);
141 if (absolute.has_scheme()) {
142 if (extension()->location() == 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* existing_window =
176 AppWindowRegistry::Get(browser_context())
177 ->GetAppWindowForAppAndKey(extension_id(),
178 create_params.window_key);
179 if (existing_window) {
180 content::RenderFrameHost* existing_frame =
181 existing_window->web_contents()->GetMainFrame();
182 int frame_id = MSG_ROUTING_NONE;
183 if (render_frame_host()->GetProcess()->GetID() ==
184 existing_frame->GetProcess()->GetID()) {
185 frame_id = existing_frame->GetRoutingID();
188 if (!options->hidden.get() || !*options->hidden.get()) {
189 if (options->focused.get() && !*options->focused.get())
190 existing_window->Show(AppWindow::SHOW_INACTIVE);
191 else
192 existing_window->Show(AppWindow::SHOW_ACTIVE);
195 base::DictionaryValue* result = new base::DictionaryValue;
196 result->Set("frameId", new base::FundamentalValue(frame_id));
197 existing_window->GetSerializedState(result);
198 result->SetBoolean("existingWindow", true);
199 // TODO(benwells): Remove HTML titlebar injection.
200 result->SetBoolean("injectTitlebar", false);
201 SetResult(result);
202 SendResponse(true);
203 return true;
208 if (!GetBoundsSpec(*options, &create_params, &error_))
209 return false;
211 if (options->type == app_window::WINDOW_TYPE_PANEL) {
212 #if defined(OS_CHROMEOS)
213 // Panels for v2 apps are only supported on Chrome OS.
214 create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
215 #else
216 WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING,
217 "Panels are not supported on this platform");
218 #endif
221 if (!GetFrameOptions(*options, &create_params))
222 return false;
224 if (extension()->GetType() == Manifest::TYPE_EXTENSION) {
225 // Whitelisted IME extensions are allowed to use this API to create IME
226 // specific windows to show accented characters or suggestions.
227 if (!extension()->permissions_data()->HasAPIPermission(
228 APIPermission::kImeWindowEnabled)) {
229 error_ = app_window_constants::kImeWindowMissingPermission;
230 return false;
233 #if !defined(OS_CHROMEOS)
234 // IME window is only supported on ChromeOS.
235 error_ = app_window_constants::kImeWindowUnsupportedPlatform;
236 return false;
237 #else
238 // IME extensions must create window with "ime: true" and "frame: none".
239 if (!options->ime.get() || !*options->ime.get() ||
240 create_params.frame != AppWindow::FRAME_NONE) {
241 error_ = app_window_constants::kImeOptionMustBeTrueAndNeedsFrameNone;
242 return false;
244 create_params.is_ime_window = true;
245 #endif // OS_CHROMEOS
246 } else {
247 if (options->ime.get()) {
248 error_ = app_window_constants::kImeOptionIsNotSupported;
249 return false;
253 if (options->alpha_enabled.get()) {
254 const char* const kWhitelist[] = {
255 #if defined(OS_CHROMEOS)
256 "B58B99751225318C7EB8CF4688B5434661083E07", // http://crbug.com/410550
257 "06BE211D5F014BAB34BC22D9DDA09C63A81D828E", // http://crbug.com/425539
258 "F94EE6AB36D6C6588670B2B01EB65212D9C64E33",
259 "B9EF10DDFEA11EF77873CC5009809E5037FC4C7A", // http://crbug.com/435380
260 #endif
261 "0F42756099D914A026DADFA182871C015735DD95", // http://crbug.com/323773
262 "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7",
263 "E7E2461CE072DF036CF9592740196159E2D7C089", // http://crbug.com/356200
264 "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3",
265 "312745D9BF916161191143F6490085EEA0434997",
266 "53041A2FA309EECED01FFC751E7399186E860B2C",
267 "A07A5B743CD82A1C2579DB77D353C98A23201EEF", // http://crbug.com/413748
268 "F16F23C83C5F6DAD9B65A120448B34056DD80691",
269 "0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B"
271 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
272 !SimpleFeature::IsIdInArray(
273 extension_id(), kWhitelist, arraysize(kWhitelist))) {
274 error_ = app_window_constants::kAlphaEnabledWrongChannel;
275 return false;
277 if (!extension()->permissions_data()->HasAPIPermission(
278 APIPermission::kAlphaEnabled)) {
279 error_ = app_window_constants::kAlphaEnabledMissingPermission;
280 return false;
282 if (create_params.frame != AppWindow::FRAME_NONE) {
283 error_ = app_window_constants::kAlphaEnabledNeedsFrameNone;
284 return false;
286 #if defined(USE_AURA)
287 create_params.alpha_enabled = *options->alpha_enabled;
288 #else
289 // Transparency is only supported on Aura.
290 // Fallback to creating an opaque window (by ignoring alphaEnabled).
291 #endif
294 if (options->hidden.get())
295 create_params.hidden = *options->hidden.get();
297 if (options->resizable.get())
298 create_params.resizable = *options->resizable.get();
300 if (options->always_on_top.get()) {
301 create_params.always_on_top = *options->always_on_top.get();
303 if (create_params.always_on_top &&
304 !extension()->permissions_data()->HasAPIPermission(
305 APIPermission::kAlwaysOnTopWindows)) {
306 error_ = app_window_constants::kAlwaysOnTopPermission;
307 return false;
311 if (options->focused.get())
312 create_params.focused = *options->focused.get();
314 if (options->visible_on_all_workspaces.get()) {
315 create_params.visible_on_all_workspaces =
316 *options->visible_on_all_workspaces.get();
319 if (options->type != app_window::WINDOW_TYPE_PANEL) {
320 switch (options->state) {
321 case app_window::STATE_NONE:
322 case app_window::STATE_NORMAL:
323 break;
324 case app_window::STATE_FULLSCREEN:
325 create_params.state = ui::SHOW_STATE_FULLSCREEN;
326 break;
327 case app_window::STATE_MAXIMIZED:
328 create_params.state = ui::SHOW_STATE_MAXIMIZED;
329 break;
330 case app_window::STATE_MINIMIZED:
331 create_params.state = ui::SHOW_STATE_MINIMIZED;
332 break;
337 create_params.creator_process_id =
338 render_frame_host()->GetProcess()->GetID();
340 AppWindow* app_window =
341 AppWindowClient::Get()->CreateAppWindow(browser_context(), extension());
342 app_window->Init(url, new AppWindowContentsImpl(app_window), create_params);
344 if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode() &&
345 !app_window->is_ime_window()) {
346 app_window->ForcedFullscreen();
349 content::RenderFrameHost* created_frame =
350 app_window->web_contents()->GetMainFrame();
351 int frame_id = MSG_ROUTING_NONE;
352 if (create_params.creator_process_id == created_frame->GetProcess()->GetID())
353 frame_id = created_frame->GetRoutingID();
355 base::DictionaryValue* result = new base::DictionaryValue;
356 result->Set("frameId", new base::FundamentalValue(frame_id));
357 result->Set("injectTitlebar",
358 new base::FundamentalValue(inject_html_titlebar_));
359 result->Set("id", new base::StringValue(app_window->window_key()));
360 app_window->GetSerializedState(result);
361 SetResult(result);
363 if (AppWindowRegistry::Get(browser_context())
364 ->HadDevToolsAttached(app_window->web_contents())) {
365 AppWindowClient::Get()->OpenDevToolsWindow(
366 app_window->web_contents(),
367 base::Bind(&AppWindowCreateFunction::SendResponse, this, true));
368 return true;
371 // PlzNavigate: delay sending the response until the newly created window has
372 // been told to navigate, and blink has been correctly initialized in the
373 // renderer.
374 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
375 ::switches::kEnableBrowserSideNavigation)) {
376 app_window->SetOnFirstCommitCallback(
377 base::Bind(&AppWindowCreateFunction::SendResponse, this, true));
378 return true;
381 SendResponse(true);
382 app_window->WindowEventsReady();
384 return true;
387 bool AppWindowCreateFunction::GetBoundsSpec(
388 const app_window::CreateWindowOptions& options,
389 AppWindow::CreateParams* params,
390 std::string* error) {
391 DCHECK(params);
392 DCHECK(error);
394 if (options.inner_bounds.get() || options.outer_bounds.get()) {
395 // Parse the inner and outer bounds specifications. If developers use the
396 // new API, the deprecated fields will be ignored - do not attempt to merge
397 // them.
399 const app_window::BoundsSpecification* inner_bounds =
400 options.inner_bounds.get();
401 const app_window::BoundsSpecification* outer_bounds =
402 options.outer_bounds.get();
403 if (inner_bounds && outer_bounds) {
404 if (!CheckBoundsConflict(
405 inner_bounds->left, outer_bounds->left, "left", error)) {
406 return false;
408 if (!CheckBoundsConflict(
409 inner_bounds->top, outer_bounds->top, "top", error)) {
410 return false;
412 if (!CheckBoundsConflict(
413 inner_bounds->width, outer_bounds->width, "width", error)) {
414 return false;
416 if (!CheckBoundsConflict(
417 inner_bounds->height, outer_bounds->height, "height", error)) {
418 return false;
420 if (!CheckBoundsConflict(inner_bounds->min_width,
421 outer_bounds->min_width,
422 "minWidth",
423 error)) {
424 return false;
426 if (!CheckBoundsConflict(inner_bounds->min_height,
427 outer_bounds->min_height,
428 "minHeight",
429 error)) {
430 return false;
432 if (!CheckBoundsConflict(inner_bounds->max_width,
433 outer_bounds->max_width,
434 "maxWidth",
435 error)) {
436 return false;
438 if (!CheckBoundsConflict(inner_bounds->max_height,
439 outer_bounds->max_height,
440 "maxHeight",
441 error)) {
442 return false;
446 CopyBoundsSpec(inner_bounds, &(params->content_spec));
447 CopyBoundsSpec(outer_bounds, &(params->window_spec));
448 } else {
449 // Parse deprecated fields.
450 // Due to a bug in NativeAppWindow::GetFrameInsets() on Windows and ChromeOS
451 // the bounds set the position of the window and the size of the content.
452 // This will be preserved as apps may be relying on this behavior.
454 if (options.default_width.get())
455 params->content_spec.bounds.set_width(*options.default_width.get());
456 if (options.default_height.get())
457 params->content_spec.bounds.set_height(*options.default_height.get());
458 if (options.default_left.get())
459 params->window_spec.bounds.set_x(*options.default_left.get());
460 if (options.default_top.get())
461 params->window_spec.bounds.set_y(*options.default_top.get());
463 if (options.width.get())
464 params->content_spec.bounds.set_width(*options.width.get());
465 if (options.height.get())
466 params->content_spec.bounds.set_height(*options.height.get());
467 if (options.left.get())
468 params->window_spec.bounds.set_x(*options.left.get());
469 if (options.top.get())
470 params->window_spec.bounds.set_y(*options.top.get());
472 if (options.bounds.get()) {
473 app_window::ContentBounds* bounds = options.bounds.get();
474 if (bounds->width.get())
475 params->content_spec.bounds.set_width(*bounds->width.get());
476 if (bounds->height.get())
477 params->content_spec.bounds.set_height(*bounds->height.get());
478 if (bounds->left.get())
479 params->window_spec.bounds.set_x(*bounds->left.get());
480 if (bounds->top.get())
481 params->window_spec.bounds.set_y(*bounds->top.get());
484 gfx::Size& minimum_size = params->content_spec.minimum_size;
485 if (options.min_width.get())
486 minimum_size.set_width(*options.min_width);
487 if (options.min_height.get())
488 minimum_size.set_height(*options.min_height);
489 gfx::Size& maximum_size = params->content_spec.maximum_size;
490 if (options.max_width.get())
491 maximum_size.set_width(*options.max_width);
492 if (options.max_height.get())
493 maximum_size.set_height(*options.max_height);
496 return true;
499 AppWindow::Frame AppWindowCreateFunction::GetFrameFromString(
500 const std::string& frame_string) {
501 if (frame_string == kHtmlFrameOption &&
502 (extension()->permissions_data()->HasAPIPermission(
503 APIPermission::kExperimental) ||
504 base::CommandLine::ForCurrentProcess()->HasSwitch(
505 switches::kEnableExperimentalExtensionApis))) {
506 inject_html_titlebar_ = true;
507 return AppWindow::FRAME_NONE;
510 if (frame_string == kNoneFrameOption)
511 return AppWindow::FRAME_NONE;
513 return AppWindow::FRAME_CHROME;
516 bool AppWindowCreateFunction::GetFrameOptions(
517 const app_window::CreateWindowOptions& options,
518 AppWindow::CreateParams* create_params) {
519 if (!options.frame)
520 return true;
522 DCHECK(options.frame->as_string || options.frame->as_frame_options);
523 if (options.frame->as_string) {
524 create_params->frame = GetFrameFromString(*options.frame->as_string);
525 return true;
528 if (options.frame->as_frame_options->type)
529 create_params->frame =
530 GetFrameFromString(*options.frame->as_frame_options->type);
532 if (options.frame->as_frame_options->color.get()) {
533 if (create_params->frame != AppWindow::FRAME_CHROME) {
534 error_ = app_window_constants::kColorWithFrameNone;
535 return false;
538 if (!image_util::ParseCSSColorString(
539 *options.frame->as_frame_options->color,
540 &create_params->active_frame_color)) {
541 error_ = app_window_constants::kInvalidColorSpecification;
542 return false;
545 create_params->has_frame_color = true;
546 create_params->inactive_frame_color = create_params->active_frame_color;
548 if (options.frame->as_frame_options->inactive_color.get()) {
549 if (!image_util::ParseCSSColorString(
550 *options.frame->as_frame_options->inactive_color,
551 &create_params->inactive_frame_color)) {
552 error_ = app_window_constants::kInvalidColorSpecification;
553 return false;
557 return true;
560 if (options.frame->as_frame_options->inactive_color.get()) {
561 error_ = app_window_constants::kInactiveColorWithoutColor;
562 return false;
565 return true;
568 } // namespace extensions