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/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"
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 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.";
68 const char kImeOptionMustBeTrueAndNeedsFrameNone
[] =
69 "IME extensions must create window with \"ime: true\" and "
72 } // namespace app_window_constants
74 const char kNoneFrameOption
[] = "none";
75 // TODO(benwells): Remove HTML titlebar injection.
76 const char kHtmlFrameOption
[] = "experimental-html";
80 // If the same property is specified for the inner and outer bounds, raise an
82 bool CheckBoundsConflict(const scoped_ptr
<int>& inner_property
,
83 const scoped_ptr
<int>& outer_property
,
84 const std::string
& property_name
,
86 if (inner_property
.get() && outer_property
.get()) {
87 std::vector
<std::string
> subst
;
88 subst
.push_back(property_name
);
89 *error
= ReplaceStringPlaceholders(
90 app_window_constants::kConflictingBoundsOptions
, subst
, NULL
);
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
) {
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
);
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())
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
138 GURL absolute
= GURL(params
->url
);
139 if (absolute
.has_scheme()) {
140 if (extension()->location() == Manifest::COMPONENT
) {
143 // Show error when url passed isn't local.
144 error_
= app_window_constants::kInvalidUrlParameter
;
149 // TODO(jeremya): figure out a way to pass the opening WebContents through to
150 // AppWindow::Create so we can set the opener at create time rather than
151 // with a hack in AppWindowCustomBindings::GetView().
152 AppWindow::CreateParams create_params
;
153 app_window::CreateWindowOptions
* options
= params
->options
.get();
155 if (options
->id
.get()) {
156 // TODO(mek): use URL if no id specified?
157 // Limit length of id to 256 characters.
158 if (options
->id
->length() > 256) {
159 error_
= app_window_constants::kInvalidWindowId
;
163 create_params
.window_key
= *options
->id
;
165 if (options
->singleton
&& *options
->singleton
== false) {
167 content::CONSOLE_MESSAGE_LEVEL_WARNING
,
168 "The 'singleton' option in chrome.apps.window.create() is deprecated!"
169 " Change your code to no longer rely on this.");
172 if (!options
->singleton
|| *options
->singleton
) {
173 AppWindow
* window
= AppWindowRegistry::Get(browser_context())
174 ->GetAppWindowForAppAndKey(
175 extension_id(), create_params
.window_key
);
177 content::RenderViewHost
* created_view
=
178 window
->web_contents()->GetRenderViewHost();
179 int view_id
= MSG_ROUTING_NONE
;
180 if (render_view_host_
->GetProcess()->GetID() ==
181 created_view
->GetProcess()->GetID()) {
182 view_id
= created_view
->GetRoutingID();
185 if (!options
->hidden
.get() || !*options
->hidden
.get()) {
186 if (options
->focused
.get() && !*options
->focused
.get())
187 window
->Show(AppWindow::SHOW_INACTIVE
);
189 window
->Show(AppWindow::SHOW_ACTIVE
);
192 base::DictionaryValue
* result
= new base::DictionaryValue
;
193 result
->Set("viewId", new base::FundamentalValue(view_id
));
194 window
->GetSerializedState(result
);
195 result
->SetBoolean("existingWindow", true);
196 // TODO(benwells): Remove HTML titlebar injection.
197 result
->SetBoolean("injectTitlebar", false);
205 if (!GetBoundsSpec(*options
, &create_params
, &error_
))
208 if (!AppWindowClient::Get()->IsCurrentChannelOlderThanDev() ||
209 extension()->location() == Manifest::COMPONENT
) {
210 if (options
->type
== app_window::WINDOW_TYPE_PANEL
) {
211 create_params
.window_type
= AppWindow::WINDOW_TYPE_PANEL
;
215 if (!GetFrameOptions(*options
, &create_params
))
218 if (extension()->GetType() == Manifest::TYPE_EXTENSION
) {
219 // Whitelisted IME extensions are allowed to use this API to create IME
220 // specific windows to show accented characters or suggestions.
221 if (!extension()->permissions_data()->HasAPIPermission(
222 APIPermission::kImeWindowEnabled
)) {
223 error_
= app_window_constants::kImeWindowMissingPermission
;
227 #if !defined(OS_CHROMEOS)
228 // IME window is only supported on ChromeOS.
229 error_
= app_window_constants::kImeWindowUnsupportedPlatform
;
232 // IME extensions must create window with "ime: true" and "frame: none".
233 if (!options
->ime
.get() || !*options
->ime
.get() ||
234 create_params
.frame
!= AppWindow::FRAME_NONE
) {
235 error_
= app_window_constants::kImeOptionMustBeTrueAndNeedsFrameNone
;
238 create_params
.is_ime_window
= true;
239 #endif // OS_CHROMEOS
241 if (options
->ime
.get()) {
242 error_
= app_window_constants::kImeOptionIsNotSupported
;
247 if (options
->alpha_enabled
.get()) {
248 const char* const kWhitelist
[] = {
249 #if defined(OS_CHROMEOS)
250 "B58B99751225318C7EB8CF4688B5434661083E07", // http://crbug.com/410550
251 "06BE211D5F014BAB34BC22D9DDA09C63A81D828E", // http://crbug.com/425539
252 "F94EE6AB36D6C6588670B2B01EB65212D9C64E33",
253 "B9EF10DDFEA11EF77873CC5009809E5037FC4C7A", // http://crbug.com/435380
255 "0F42756099D914A026DADFA182871C015735DD95", // http://crbug.com/323773
256 "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7",
257 "E7E2461CE072DF036CF9592740196159E2D7C089", // http://crbug.com/356200
258 "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3",
259 "312745D9BF916161191143F6490085EEA0434997",
260 "53041A2FA309EECED01FFC751E7399186E860B2C",
261 "A07A5B743CD82A1C2579DB77D353C98A23201EEF", // http://crbug.com/413748
262 "F16F23C83C5F6DAD9B65A120448B34056DD80691",
263 "0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B"
265 if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
266 !SimpleFeature::IsIdInArray(
267 extension_id(), kWhitelist
, arraysize(kWhitelist
))) {
268 error_
= app_window_constants::kAlphaEnabledWrongChannel
;
271 if (!extension()->permissions_data()->HasAPIPermission(
272 APIPermission::kAlphaEnabled
)) {
273 error_
= app_window_constants::kAlphaEnabledMissingPermission
;
276 if (create_params
.frame
!= AppWindow::FRAME_NONE
) {
277 error_
= app_window_constants::kAlphaEnabledNeedsFrameNone
;
280 #if defined(USE_AURA)
281 create_params
.alpha_enabled
= *options
->alpha_enabled
;
283 // Transparency is only supported on Aura.
284 // Fallback to creating an opaque window (by ignoring alphaEnabled).
288 if (options
->hidden
.get())
289 create_params
.hidden
= *options
->hidden
.get();
291 if (options
->resizable
.get())
292 create_params
.resizable
= *options
->resizable
.get();
294 if (options
->always_on_top
.get()) {
295 create_params
.always_on_top
= *options
->always_on_top
.get();
297 if (create_params
.always_on_top
&&
298 !extension()->permissions_data()->HasAPIPermission(
299 APIPermission::kAlwaysOnTopWindows
)) {
300 error_
= app_window_constants::kAlwaysOnTopPermission
;
305 if (options
->focused
.get())
306 create_params
.focused
= *options
->focused
.get();
308 if (options
->visible_on_all_workspaces
.get()) {
309 create_params
.visible_on_all_workspaces
=
310 *options
->visible_on_all_workspaces
.get();
313 if (options
->type
!= app_window::WINDOW_TYPE_PANEL
) {
314 switch (options
->state
) {
315 case app_window::STATE_NONE
:
316 case app_window::STATE_NORMAL
:
318 case app_window::STATE_FULLSCREEN
:
319 create_params
.state
= ui::SHOW_STATE_FULLSCREEN
;
321 case app_window::STATE_MAXIMIZED
:
322 create_params
.state
= ui::SHOW_STATE_MAXIMIZED
;
324 case app_window::STATE_MINIMIZED
:
325 create_params
.state
= ui::SHOW_STATE_MINIMIZED
;
331 create_params
.creator_process_id
=
332 render_view_host_
->GetProcess()->GetID();
334 AppWindow
* app_window
=
335 AppWindowClient::Get()->CreateAppWindow(browser_context(), extension());
336 app_window
->Init(url
, new AppWindowContentsImpl(app_window
), create_params
);
338 if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode() &&
339 !app_window
->is_ime_window()) {
340 app_window
->ForcedFullscreen();
343 content::RenderViewHost
* created_view
=
344 app_window
->web_contents()->GetRenderViewHost();
345 int view_id
= MSG_ROUTING_NONE
;
346 if (create_params
.creator_process_id
== created_view
->GetProcess()->GetID())
347 view_id
= created_view
->GetRoutingID();
349 base::DictionaryValue
* result
= new base::DictionaryValue
;
350 result
->Set("viewId", new base::FundamentalValue(view_id
));
351 result
->Set("injectTitlebar",
352 new base::FundamentalValue(inject_html_titlebar_
));
353 result
->Set("id", new base::StringValue(app_window
->window_key()));
354 app_window
->GetSerializedState(result
);
357 if (AppWindowRegistry::Get(browser_context())
358 ->HadDevToolsAttached(created_view
)) {
359 AppWindowClient::Get()->OpenDevToolsWindow(
360 app_window
->web_contents(),
361 base::Bind(&AppWindowCreateFunction::SendResponse
, this, true));
366 app_window
->WindowEventsReady();
371 bool AppWindowCreateFunction::GetBoundsSpec(
372 const app_window::CreateWindowOptions
& options
,
373 AppWindow::CreateParams
* params
,
374 std::string
* error
) {
378 if (options
.inner_bounds
.get() || options
.outer_bounds
.get()) {
379 // Parse the inner and outer bounds specifications. If developers use the
380 // new API, the deprecated fields will be ignored - do not attempt to merge
383 const app_window::BoundsSpecification
* inner_bounds
=
384 options
.inner_bounds
.get();
385 const app_window::BoundsSpecification
* outer_bounds
=
386 options
.outer_bounds
.get();
387 if (inner_bounds
&& outer_bounds
) {
388 if (!CheckBoundsConflict(
389 inner_bounds
->left
, outer_bounds
->left
, "left", error
)) {
392 if (!CheckBoundsConflict(
393 inner_bounds
->top
, outer_bounds
->top
, "top", error
)) {
396 if (!CheckBoundsConflict(
397 inner_bounds
->width
, outer_bounds
->width
, "width", error
)) {
400 if (!CheckBoundsConflict(
401 inner_bounds
->height
, outer_bounds
->height
, "height", error
)) {
404 if (!CheckBoundsConflict(inner_bounds
->min_width
,
405 outer_bounds
->min_width
,
410 if (!CheckBoundsConflict(inner_bounds
->min_height
,
411 outer_bounds
->min_height
,
416 if (!CheckBoundsConflict(inner_bounds
->max_width
,
417 outer_bounds
->max_width
,
422 if (!CheckBoundsConflict(inner_bounds
->max_height
,
423 outer_bounds
->max_height
,
430 CopyBoundsSpec(inner_bounds
, &(params
->content_spec
));
431 CopyBoundsSpec(outer_bounds
, &(params
->window_spec
));
433 // Parse deprecated fields.
434 // Due to a bug in NativeAppWindow::GetFrameInsets() on Windows and ChromeOS
435 // the bounds set the position of the window and the size of the content.
436 // This will be preserved as apps may be relying on this behavior.
438 if (options
.default_width
.get())
439 params
->content_spec
.bounds
.set_width(*options
.default_width
.get());
440 if (options
.default_height
.get())
441 params
->content_spec
.bounds
.set_height(*options
.default_height
.get());
442 if (options
.default_left
.get())
443 params
->window_spec
.bounds
.set_x(*options
.default_left
.get());
444 if (options
.default_top
.get())
445 params
->window_spec
.bounds
.set_y(*options
.default_top
.get());
447 if (options
.width
.get())
448 params
->content_spec
.bounds
.set_width(*options
.width
.get());
449 if (options
.height
.get())
450 params
->content_spec
.bounds
.set_height(*options
.height
.get());
451 if (options
.left
.get())
452 params
->window_spec
.bounds
.set_x(*options
.left
.get());
453 if (options
.top
.get())
454 params
->window_spec
.bounds
.set_y(*options
.top
.get());
456 if (options
.bounds
.get()) {
457 app_window::ContentBounds
* bounds
= options
.bounds
.get();
458 if (bounds
->width
.get())
459 params
->content_spec
.bounds
.set_width(*bounds
->width
.get());
460 if (bounds
->height
.get())
461 params
->content_spec
.bounds
.set_height(*bounds
->height
.get());
462 if (bounds
->left
.get())
463 params
->window_spec
.bounds
.set_x(*bounds
->left
.get());
464 if (bounds
->top
.get())
465 params
->window_spec
.bounds
.set_y(*bounds
->top
.get());
468 gfx::Size
& minimum_size
= params
->content_spec
.minimum_size
;
469 if (options
.min_width
.get())
470 minimum_size
.set_width(*options
.min_width
);
471 if (options
.min_height
.get())
472 minimum_size
.set_height(*options
.min_height
);
473 gfx::Size
& maximum_size
= params
->content_spec
.maximum_size
;
474 if (options
.max_width
.get())
475 maximum_size
.set_width(*options
.max_width
);
476 if (options
.max_height
.get())
477 maximum_size
.set_height(*options
.max_height
);
483 AppWindow::Frame
AppWindowCreateFunction::GetFrameFromString(
484 const std::string
& frame_string
) {
485 if (frame_string
== kHtmlFrameOption
&&
486 (extension()->permissions_data()->HasAPIPermission(
487 APIPermission::kExperimental
) ||
488 base::CommandLine::ForCurrentProcess()->HasSwitch(
489 switches::kEnableExperimentalExtensionApis
))) {
490 inject_html_titlebar_
= true;
491 return AppWindow::FRAME_NONE
;
494 if (frame_string
== kNoneFrameOption
)
495 return AppWindow::FRAME_NONE
;
497 return AppWindow::FRAME_CHROME
;
500 bool AppWindowCreateFunction::GetFrameOptions(
501 const app_window::CreateWindowOptions
& options
,
502 AppWindow::CreateParams
* create_params
) {
506 DCHECK(options
.frame
->as_string
|| options
.frame
->as_frame_options
);
507 if (options
.frame
->as_string
) {
508 create_params
->frame
= GetFrameFromString(*options
.frame
->as_string
);
512 if (options
.frame
->as_frame_options
->type
)
513 create_params
->frame
=
514 GetFrameFromString(*options
.frame
->as_frame_options
->type
);
516 if (options
.frame
->as_frame_options
->color
.get()) {
517 if (create_params
->frame
!= AppWindow::FRAME_CHROME
) {
518 error_
= app_window_constants::kColorWithFrameNone
;
522 if (!image_util::ParseCSSColorString(
523 *options
.frame
->as_frame_options
->color
,
524 &create_params
->active_frame_color
)) {
525 error_
= app_window_constants::kInvalidColorSpecification
;
529 create_params
->has_frame_color
= true;
530 create_params
->inactive_frame_color
= create_params
->active_frame_color
;
532 if (options
.frame
->as_frame_options
->inactive_color
.get()) {
533 if (!image_util::ParseCSSColorString(
534 *options
.frame
->as_frame_options
->inactive_color
,
535 &create_params
->inactive_frame_color
)) {
536 error_
= app_window_constants::kInvalidColorSpecification
;
544 if (options
.frame
->as_frame_options
->inactive_color
.get()) {
545 error_
= app_window_constants::kInactiveColorWithoutColor
;
552 } // namespace extensions