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"
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
= base::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 // 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
) {
144 // Show error when url passed isn't local.
145 error_
= app_window_constants::kInvalidUrlParameter
;
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();
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
;
164 create_params
.window_key
= *options
->id
;
166 if (options
->singleton
&& *options
->singleton
== false) {
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
);
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);
207 if (!GetBoundsSpec(*options
, &create_params
, &error_
))
210 if (options
->type
== app_window::WINDOW_TYPE_PANEL
) {
212 // Currently panels for v2 apps are only implemented in Ash.
213 create_params
.window_type
= AppWindow::WINDOW_TYPE_PANEL
;
215 WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING
,
216 "Panels are not supported on this platform");
220 if (!GetFrameOptions(*options
, &create_params
))
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
;
232 #if !defined(OS_CHROMEOS)
233 // IME window is only supported on ChromeOS.
234 error_
= app_window_constants::kImeWindowUnsupportedPlatform
;
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
;
243 create_params
.is_ime_window
= true;
244 #endif // OS_CHROMEOS
246 if (options
->ime
.get()) {
247 error_
= app_window_constants::kImeOptionIsNotSupported
;
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
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
;
276 if (!extension()->permissions_data()->HasAPIPermission(
277 APIPermission::kAlphaEnabled
)) {
278 error_
= app_window_constants::kAlphaEnabledMissingPermission
;
281 if (create_params
.frame
!= AppWindow::FRAME_NONE
) {
282 error_
= app_window_constants::kAlphaEnabledNeedsFrameNone
;
285 #if defined(USE_AURA)
286 create_params
.alpha_enabled
= *options
->alpha_enabled
;
288 // Transparency is only supported on Aura.
289 // Fallback to creating an opaque window (by ignoring alphaEnabled).
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
;
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
:
323 case app_window::STATE_FULLSCREEN
:
324 create_params
.state
= ui::SHOW_STATE_FULLSCREEN
;
326 case app_window::STATE_MAXIMIZED
:
327 create_params
.state
= ui::SHOW_STATE_MAXIMIZED
;
329 case app_window::STATE_MINIMIZED
:
330 create_params
.state
= ui::SHOW_STATE_MINIMIZED
;
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
);
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));
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 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
) {
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