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"
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.";
70 const char kImeOptionMustBeTrueAndNeedsFrameNone
[] =
71 "IME extensions must create window with \"ime: true\" and "
74 } // namespace app_window_constants
76 const char kNoneFrameOption
[] = "none";
77 // TODO(benwells): Remove HTML titlebar injection.
78 const char kHtmlFrameOption
[] = "experimental-html";
82 // If the same property is specified for the inner and outer bounds, raise an
84 bool CheckBoundsConflict(const scoped_ptr
<int>& inner_property
,
85 const scoped_ptr
<int>& outer_property
,
86 const std::string
& property_name
,
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
);
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
) {
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
);
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())
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
140 GURL absolute
= GURL(params
->url
);
141 if (absolute
.has_scheme()) {
142 if (extension()->location() == extensions::Manifest::COMPONENT
) {
145 // Show error when url passed isn't local.
146 error_
= app_window_constants::kInvalidUrlParameter
;
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();
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
;
165 create_params
.window_key
= *options
->id
;
167 if (options
->singleton
&& *options
->singleton
== false) {
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
);
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
);
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);
207 if (!GetBoundsSpec(*options
, &create_params
, &error_
))
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
))
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
;
229 #if !defined(OS_CHROMEOS)
230 // IME window is only supported on ChromeOS.
231 error_
= app_window_constants::kImeWindowUnsupportedPlatform
;
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
;
240 create_params
.is_ime_window
= true;
241 #endif // OS_CHROMEOS
243 if (options
->ime
.get()) {
244 error_
= app_window_constants::kImeOptionIsNotSupported
;
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",
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(
269 std::set
<std::string
>(whitelist
,
270 whitelist
+ arraysize(whitelist
)))) {
271 error_
= app_window_constants::kAlphaEnabledWrongChannel
;
274 if (!extension()->permissions_data()->HasAPIPermission(
275 APIPermission::kAlphaEnabled
)) {
276 error_
= app_window_constants::kAlphaEnabledMissingPermission
;
279 if (create_params
.frame
!= AppWindow::FRAME_NONE
) {
280 error_
= app_window_constants::kAlphaEnabledNeedsFrameNone
;
283 #if defined(USE_AURA)
284 create_params
.alpha_enabled
= *options
->alpha_enabled
;
286 // Transparency is only supported on Aura.
287 // Fallback to creating an opaque window (by ignoring alphaEnabled).
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
;
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
;
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
:
325 case app_window::STATE_FULLSCREEN
:
326 create_params
.state
= ui::SHOW_STATE_FULLSCREEN
;
328 case app_window::STATE_MAXIMIZED
:
329 create_params
.state
= ui::SHOW_STATE_MAXIMIZED
;
331 case app_window::STATE_MINIMIZED
:
332 create_params
.state
= ui::SHOW_STATE_MINIMIZED
;
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
);
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));
371 app_window
->WindowEventsReady();
376 bool AppWindowCreateFunction::GetBoundsSpec(
377 const app_window::CreateWindowOptions
& options
,
378 AppWindow::CreateParams
* params
,
379 std::string
* 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
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
)) {
397 if (!CheckBoundsConflict(
398 inner_bounds
->top
, outer_bounds
->top
, "top", error
)) {
401 if (!CheckBoundsConflict(
402 inner_bounds
->width
, outer_bounds
->width
, "width", error
)) {
405 if (!CheckBoundsConflict(
406 inner_bounds
->height
, outer_bounds
->height
, "height", error
)) {
409 if (!CheckBoundsConflict(inner_bounds
->min_width
,
410 outer_bounds
->min_width
,
415 if (!CheckBoundsConflict(inner_bounds
->min_height
,
416 outer_bounds
->min_height
,
421 if (!CheckBoundsConflict(inner_bounds
->max_width
,
422 outer_bounds
->max_width
,
427 if (!CheckBoundsConflict(inner_bounds
->max_height
,
428 outer_bounds
->max_height
,
435 CopyBoundsSpec(inner_bounds
, &(params
->content_spec
));
436 CopyBoundsSpec(outer_bounds
, &(params
->window_spec
));
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
);
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
) {
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
);
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
;
527 if (!image_util::ParseCSSColorString(
528 *options
.frame
->as_frame_options
->color
,
529 &create_params
->active_frame_color
)) {
530 error_
= app_window_constants::kInvalidColorSpecification
;
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
;
549 if (options
.frame
->as_frame_options
->inactive_color
.get()) {
550 error_
= app_window_constants::kInactiveColorWithoutColor
;
557 } // namespace extensions