Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / extensions / browser / api / app_window / app_window_api.cc
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.
4
5 #include "extensions/browser/api/app_window/app_window_api.h"
6
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"
33 #include "url/gurl.h"
34
35 namespace app_window = extensions::core_api::app_window;
36 namespace Create = app_window::Create;
37
38 namespace extensions {
39
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.";
69 #else
70 const char kImeOptionMustBeTrueAndNeedsFrameNone[] =
71     "IME extensions must create window with \"ime: true\" and "
72     "\"frame: 'none'\".";
73 #endif
74 }  // namespace app_window_constants
75
76 const char kNoneFrameOption[] = "none";
77   // TODO(benwells): Remove HTML titlebar injection.
78 const char kHtmlFrameOption[] = "experimental-html";
79
80 namespace {
81
82 // If the same property is specified for the inner and outer bounds, raise an
83 // error.
84 bool CheckBoundsConflict(const scoped_ptr<int>& inner_property,
85                          const scoped_ptr<int>& outer_property,
86                          const std::string& property_name,
87                          std::string* error) {
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);
93     return false;
94   }
95
96   return true;
97 }
98
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) {
103   if (!input_spec)
104     return;
105
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);
122 }
123
124 }  // namespace
125
126 AppWindowCreateFunction::AppWindowCreateFunction()
127     : inject_html_titlebar_(false) {}
128
129 bool AppWindowCreateFunction::RunAsync() {
130   // Don't create app window if the system is shutting down.
131   if (extensions::ExtensionsBrowserClient::Get()->IsShuttingDown())
132     return false;
133
134   scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
135   EXTENSION_FUNCTION_VALIDATE(params.get());
136
137   GURL url = extension()->GetResourceURL(params->url);
138   // Allow absolute URLs for component apps, otherwise prepend the extension
139   // path.
140   GURL absolute = GURL(params->url);
141   if (absolute.has_scheme()) {
142     if (extension()->location() == extensions::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;
148     }
149   }
150
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;
163       }
164
165       create_params.window_key = *options->id;
166
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.");
172       }
173
174       if (!options->singleton || *options->singleton) {
175         AppWindow* window = AppWindowRegistry::Get(browser_context())
176                                 ->GetAppWindowForAppAndKey(
177                                     extension_id(), create_params.window_key);
178         if (window) {
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();
185           }
186
187           if (options->hidden.get() && !*options->hidden.get()) {
188             if (options->focused.get() && !*options->focused.get())
189               window->Show(AppWindow::SHOW_INACTIVE);
190             else
191               window->Show(AppWindow::SHOW_ACTIVE);
192           }
193
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);
200           SetResult(result);
201           SendResponse(true);
202           return true;
203         }
204       }
205     }
206
207     if (!GetBoundsSpec(*options, &create_params, &error_))
208       return false;
209
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;
214       }
215     }
216
217     if (!GetFrameOptions(*options, &create_params))
218       return false;
219
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;
226         return false;
227       }
228
229 #if !defined(OS_CHROMEOS)
230       // IME window is only supported on ChromeOS.
231       error_ = app_window_constants::kImeWindowUnsupportedPlatform;
232       return false;
233 #else
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;
238         return false;
239       }
240       create_params.is_ime_window = true;
241 #endif  // OS_CHROMEOS
242     } else {
243       if (options->ime.get()) {
244         error_ = app_window_constants::kImeOptionIsNotSupported;
245         return false;
246       }
247     }
248
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",
255 #endif
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"
265       };
266       if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
267           !extensions::SimpleFeature::IsIdInList(
268               extension_id(),
269               std::set<std::string>(whitelist,
270                                     whitelist + arraysize(whitelist)))) {
271         error_ = app_window_constants::kAlphaEnabledWrongChannel;
272         return false;
273       }
274       if (!extension()->permissions_data()->HasAPIPermission(
275               APIPermission::kAlphaEnabled)) {
276         error_ = app_window_constants::kAlphaEnabledMissingPermission;
277         return false;
278       }
279       if (create_params.frame != AppWindow::FRAME_NONE) {
280         error_ = app_window_constants::kAlphaEnabledNeedsFrameNone;
281         return false;
282       }
283 #if defined(USE_AURA)
284       create_params.alpha_enabled = *options->alpha_enabled;
285 #else
286       // Transparency is only supported on Aura.
287       // Fallback to creating an opaque window (by ignoring alphaEnabled).
288 #endif
289     }
290
291     if (options->hidden.get())
292       create_params.hidden = *options->hidden.get();
293
294     if (options->resizable.get())
295       create_params.resizable = *options->resizable.get();
296
297     if (options->always_on_top.get()) {
298       create_params.always_on_top = *options->always_on_top.get();
299
300       if (create_params.always_on_top &&
301           !extension()->permissions_data()->HasAPIPermission(
302               APIPermission::kAlwaysOnTopWindows)) {
303         error_ = app_window_constants::kAlwaysOnTopPermission;
304         return false;
305       }
306     }
307
308     if (options->focused.get())
309       create_params.focused = *options->focused.get();
310
311     if (options->visible_on_all_workspaces.get()) {
312       if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) {
313         error_ = app_window_constants::kVisibleOnAllWorkspacesWrongChannel;
314         return false;
315       }
316       create_params.visible_on_all_workspaces =
317           *options->visible_on_all_workspaces.get();
318     }
319
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:
324           break;
325         case app_window::STATE_FULLSCREEN:
326           create_params.state = ui::SHOW_STATE_FULLSCREEN;
327           break;
328         case app_window::STATE_MAXIMIZED:
329           create_params.state = ui::SHOW_STATE_MAXIMIZED;
330           break;
331         case app_window::STATE_MINIMIZED:
332           create_params.state = ui::SHOW_STATE_MINIMIZED;
333           break;
334       }
335     }
336   }
337
338   create_params.creator_process_id =
339       render_view_host_->GetProcess()->GetID();
340
341   AppWindow* app_window =
342       AppWindowClient::Get()->CreateAppWindow(browser_context(), extension());
343   app_window->Init(url, new AppWindowContentsImpl(app_window), create_params);
344
345   if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode())
346     app_window->ForcedFullscreen();
347
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();
353
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);
360   SetResult(result);
361
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));
367     return true;
368   }
369
370   SendResponse(true);
371   app_window->WindowEventsReady();
372
373   return true;
374 }
375
376 bool AppWindowCreateFunction::GetBoundsSpec(
377     const app_window::CreateWindowOptions& options,
378     AppWindow::CreateParams* params,
379     std::string* error) {
380   DCHECK(params);
381   DCHECK(error);
382
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.
387
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;
396       }
397       if (!CheckBoundsConflict(
398                inner_bounds->top, outer_bounds->top, "top", error)) {
399         return false;
400       }
401       if (!CheckBoundsConflict(
402                inner_bounds->width, outer_bounds->width, "width", error)) {
403         return false;
404       }
405       if (!CheckBoundsConflict(
406                inner_bounds->height, outer_bounds->height, "height", error)) {
407         return false;
408       }
409       if (!CheckBoundsConflict(inner_bounds->min_width,
410                                outer_bounds->min_width,
411                                "minWidth",
412                                error)) {
413         return false;
414       }
415       if (!CheckBoundsConflict(inner_bounds->min_height,
416                                outer_bounds->min_height,
417                                "minHeight",
418                                error)) {
419         return false;
420       }
421       if (!CheckBoundsConflict(inner_bounds->max_width,
422                                outer_bounds->max_width,
423                                "maxWidth",
424                                error)) {
425         return false;
426       }
427       if (!CheckBoundsConflict(inner_bounds->max_height,
428                                outer_bounds->max_height,
429                                "maxHeight",
430                                error)) {
431         return false;
432       }
433     }
434
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.
442
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());
451
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());
460
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());
471     }
472
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);
483   }
484
485   return true;
486 }
487
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;
497    }
498
499    if (frame_string == kNoneFrameOption)
500     return AppWindow::FRAME_NONE;
501
502   return AppWindow::FRAME_CHROME;
503 }
504
505 bool AppWindowCreateFunction::GetFrameOptions(
506     const app_window::CreateWindowOptions& options,
507     AppWindow::CreateParams* create_params) {
508   if (!options.frame)
509     return true;
510
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;
515   }
516
517   if (options.frame->as_frame_options->type)
518     create_params->frame =
519         GetFrameFromString(*options.frame->as_frame_options->type);
520
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;
525     }
526
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;
532     }
533
534     create_params->has_frame_color = true;
535     create_params->inactive_frame_color = create_params->active_frame_color;
536
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;
543       }
544     }
545
546     return true;
547   }
548
549   if (options.frame->as_frame_options->inactive_color.get()) {
550     error_ = app_window_constants::kInactiveColorWithoutColor;
551     return false;
552   }
553
554   return true;
555 }
556
557 }  // namespace extensions