Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / 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 "chrome/browser/extensions/api/app_window/app_window_api.h"
6
7 #include "apps/app_window.h"
8 #include "apps/app_window_contents.h"
9 #include "apps/app_window_registry.h"
10 #include "apps/apps_client.h"
11 #include "apps/ui/native_app_window.h"
12 #include "base/command_line.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/time/time.h"
16 #include "base/values.h"
17 #include "chrome/browser/devtools/devtools_window.h"
18 #include "chrome/common/extensions/api/app_window.h"
19 #include "chrome/common/extensions/features/feature_channel.h"
20 #include "content/public/browser/notification_registrar.h"
21 #include "content/public/browser/notification_types.h"
22 #include "content/public/browser/render_process_host.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/common/url_constants.h"
26 #include "extensions/browser/extensions_browser_client.h"
27 #include "extensions/browser/image_util.h"
28 #include "extensions/common/switches.h"
29 #include "third_party/skia/include/core/SkColor.h"
30 #include "ui/base/ui_base_types.h"
31 #include "ui/gfx/rect.h"
32 #include "url/gurl.h"
33
34 using apps::AppWindow;
35
36 namespace app_window = extensions::api::app_window;
37 namespace Create = app_window::Create;
38
39 namespace extensions {
40
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 }  // namespace app_window_constants
52
53 const char kNoneFrameOption[] = "none";
54   // TODO(benwells): Remove HTML titlebar injection.
55 const char kHtmlFrameOption[] = "experimental-html";
56
57 namespace {
58
59 // Opens an inspector window and delays the response to the
60 // AppWindowCreateFunction until the DevToolsWindow has finished loading, and is
61 // ready to stop on breakpoints in the callback.
62 class DevToolsRestorer : public base::RefCounted<DevToolsRestorer> {
63  public:
64   DevToolsRestorer(AppWindowCreateFunction* delayed_create_function,
65                    content::RenderViewHost* created_view)
66       : delayed_create_function_(delayed_create_function) {
67     AddRef();  // Balanced in LoadCompleted.
68     DevToolsWindow* devtools_window =
69         DevToolsWindow::OpenDevToolsWindow(
70             created_view,
71             DevToolsToggleAction::ShowConsole());
72     devtools_window->SetLoadCompletedCallback(
73         base::Bind(&DevToolsRestorer::LoadCompleted, this));
74   }
75
76  private:
77   friend class base::RefCounted<DevToolsRestorer>;
78   ~DevToolsRestorer() {}
79
80   void LoadCompleted() {
81     delayed_create_function_->SendDelayedResponse();
82     Release();
83   }
84
85   scoped_refptr<AppWindowCreateFunction> delayed_create_function_;
86 };
87
88 // If the same property is specified for the inner and outer bounds, raise an
89 // error.
90 bool CheckBoundsConflict(const scoped_ptr<int>& inner_property,
91                          const scoped_ptr<int>& outer_property,
92                          const std::string& property_name,
93                          std::string* error) {
94   if (inner_property.get() && outer_property.get()) {
95     std::vector<std::string> subst;
96     subst.push_back(property_name);
97     *error = ReplaceStringPlaceholders(
98         app_window_constants::kConflictingBoundsOptions, subst, NULL);
99     return false;
100   }
101
102   return true;
103 }
104
105 // Copy over the bounds specification properties from the API to the
106 // AppWindow::CreateParams.
107 void CopyBoundsSpec(
108     const extensions::api::app_window::BoundsSpecification* input_spec,
109     apps::AppWindow::BoundsSpecification* create_spec) {
110   if (!input_spec)
111     return;
112
113   if (input_spec->left.get())
114     create_spec->bounds.set_x(*input_spec->left);
115   if (input_spec->top.get())
116     create_spec->bounds.set_y(*input_spec->top);
117   if (input_spec->width.get())
118     create_spec->bounds.set_width(*input_spec->width);
119   if (input_spec->height.get())
120     create_spec->bounds.set_height(*input_spec->height);
121   if (input_spec->min_width.get())
122     create_spec->minimum_size.set_width(*input_spec->min_width);
123   if (input_spec->min_height.get())
124     create_spec->minimum_size.set_height(*input_spec->min_height);
125   if (input_spec->max_width.get())
126     create_spec->maximum_size.set_width(*input_spec->max_width);
127   if (input_spec->max_height.get())
128     create_spec->maximum_size.set_height(*input_spec->max_height);
129 }
130
131 }  // namespace
132
133 AppWindowCreateFunction::AppWindowCreateFunction()
134     : inject_html_titlebar_(false) {}
135
136 void AppWindowCreateFunction::SendDelayedResponse() {
137   SendResponse(true);
138 }
139
140 bool AppWindowCreateFunction::RunAsync() {
141   // Don't create app window if the system is shutting down.
142   if (extensions::ExtensionsBrowserClient::Get()->IsShuttingDown())
143     return false;
144
145   scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
146   EXTENSION_FUNCTION_VALIDATE(params.get());
147
148   GURL url = GetExtension()->GetResourceURL(params->url);
149   // Allow absolute URLs for component apps, otherwise prepend the extension
150   // path.
151   if (GetExtension()->location() == extensions::Manifest::COMPONENT) {
152     GURL absolute = GURL(params->url);
153     if (absolute.has_scheme())
154       url = absolute;
155   }
156
157   // TODO(jeremya): figure out a way to pass the opening WebContents through to
158   // AppWindow::Create so we can set the opener at create time rather than
159   // with a hack in AppWindowCustomBindings::GetView().
160   AppWindow::CreateParams create_params;
161   app_window::CreateWindowOptions* options = params->options.get();
162   if (options) {
163     if (options->id.get()) {
164       // TODO(mek): use URL if no id specified?
165       // Limit length of id to 256 characters.
166       if (options->id->length() > 256) {
167         error_ = app_window_constants::kInvalidWindowId;
168         return false;
169       }
170
171       create_params.window_key = *options->id;
172
173       if (options->singleton && *options->singleton == false) {
174         WriteToConsole(
175           content::CONSOLE_MESSAGE_LEVEL_WARNING,
176           "The 'singleton' option in chrome.apps.window.create() is deprecated!"
177           " Change your code to no longer rely on this.");
178       }
179
180       if (!options->singleton || *options->singleton) {
181         AppWindow* window = apps::AppWindowRegistry::Get(browser_context())
182                                 ->GetAppWindowForAppAndKey(
183                                     extension_id(), create_params.window_key);
184         if (window) {
185           content::RenderViewHost* created_view =
186               window->web_contents()->GetRenderViewHost();
187           int view_id = MSG_ROUTING_NONE;
188           if (render_view_host_->GetProcess()->GetID() ==
189               created_view->GetProcess()->GetID()) {
190             view_id = created_view->GetRoutingID();
191           }
192
193           if (options->focused.get() && !*options->focused.get())
194             window->Show(AppWindow::SHOW_INACTIVE);
195           else
196             window->Show(AppWindow::SHOW_ACTIVE);
197
198           base::DictionaryValue* result = new base::DictionaryValue;
199           result->Set("viewId", new base::FundamentalValue(view_id));
200           window->GetSerializedState(result);
201           result->SetBoolean("existingWindow", true);
202           // TODO(benwells): Remove HTML titlebar injection.
203           result->SetBoolean("injectTitlebar", false);
204           SetResult(result);
205           SendResponse(true);
206           return true;
207         }
208       }
209     }
210
211     if (!GetBoundsSpec(*options, &create_params, &error_))
212       return false;
213
214     if (GetCurrentChannel() <= chrome::VersionInfo::CHANNEL_DEV ||
215         GetExtension()->location() == extensions::Manifest::COMPONENT) {
216       if (options->type == extensions::api::app_window::WINDOW_TYPE_PANEL) {
217         create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
218       }
219     }
220
221     if (!GetFrameOptions(*options, &create_params))
222       return false;
223
224     if (options->transparent_background.get() &&
225         (GetExtension()->HasAPIPermission(APIPermission::kExperimental) ||
226          CommandLine::ForCurrentProcess()->HasSwitch(
227              switches::kEnableExperimentalExtensionApis))) {
228       create_params.transparent_background = *options->transparent_background;
229     }
230
231     if (options->hidden.get())
232       create_params.hidden = *options->hidden.get();
233
234     if (options->resizable.get())
235       create_params.resizable = *options->resizable.get();
236
237     if (options->always_on_top.get() &&
238         GetExtension()->HasAPIPermission(APIPermission::kAlwaysOnTopWindows))
239       create_params.always_on_top = *options->always_on_top.get();
240
241     if (options->focused.get())
242       create_params.focused = *options->focused.get();
243
244     if (options->type != extensions::api::app_window::WINDOW_TYPE_PANEL) {
245       switch (options->state) {
246         case extensions::api::app_window::STATE_NONE:
247         case extensions::api::app_window::STATE_NORMAL:
248           break;
249         case extensions::api::app_window::STATE_FULLSCREEN:
250           create_params.state = ui::SHOW_STATE_FULLSCREEN;
251           break;
252         case extensions::api::app_window::STATE_MAXIMIZED:
253           create_params.state = ui::SHOW_STATE_MAXIMIZED;
254           break;
255         case extensions::api::app_window::STATE_MINIMIZED:
256           create_params.state = ui::SHOW_STATE_MINIMIZED;
257           break;
258       }
259     }
260   }
261
262   create_params.creator_process_id =
263       render_view_host_->GetProcess()->GetID();
264
265   AppWindow* app_window = apps::AppsClient::Get()->CreateAppWindow(
266       browser_context(), GetExtension());
267   app_window->Init(
268       url, new apps::AppWindowContentsImpl(app_window), create_params);
269
270   if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode())
271     app_window->ForcedFullscreen();
272
273   content::RenderViewHost* created_view =
274       app_window->web_contents()->GetRenderViewHost();
275   int view_id = MSG_ROUTING_NONE;
276   if (create_params.creator_process_id == created_view->GetProcess()->GetID())
277     view_id = created_view->GetRoutingID();
278
279   base::DictionaryValue* result = new base::DictionaryValue;
280   result->Set("viewId", new base::FundamentalValue(view_id));
281   result->Set("injectTitlebar",
282       new base::FundamentalValue(inject_html_titlebar_));
283   result->Set("id", new base::StringValue(app_window->window_key()));
284   app_window->GetSerializedState(result);
285   SetResult(result);
286
287   if (apps::AppWindowRegistry::Get(browser_context())
288           ->HadDevToolsAttached(created_view)) {
289     new DevToolsRestorer(this, created_view);
290     return true;
291   }
292
293   SendResponse(true);
294   return true;
295 }
296
297 bool AppWindowCreateFunction::GetBoundsSpec(
298     const extensions::api::app_window::CreateWindowOptions& options,
299     apps::AppWindow::CreateParams* params,
300     std::string* error) {
301   DCHECK(params);
302   DCHECK(error);
303
304   if (options.inner_bounds.get() || options.outer_bounds.get()) {
305     // Parse the inner and outer bounds specifications. If developers use the
306     // new API, the deprecated fields will be ignored - do not attempt to merge
307     // them.
308
309     const extensions::api::app_window::BoundsSpecification* inner_bounds =
310         options.inner_bounds.get();
311     const extensions::api::app_window::BoundsSpecification* outer_bounds =
312         options.outer_bounds.get();
313     if (inner_bounds && outer_bounds) {
314       if (!CheckBoundsConflict(
315                inner_bounds->left, outer_bounds->left, "left", error)) {
316         return false;
317       }
318       if (!CheckBoundsConflict(
319                inner_bounds->top, outer_bounds->top, "top", error)) {
320         return false;
321       }
322       if (!CheckBoundsConflict(
323                inner_bounds->width, outer_bounds->width, "width", error)) {
324         return false;
325       }
326       if (!CheckBoundsConflict(
327                inner_bounds->height, outer_bounds->height, "height", error)) {
328         return false;
329       }
330       if (!CheckBoundsConflict(inner_bounds->min_width,
331                                outer_bounds->min_width,
332                                "minWidth",
333                                error)) {
334         return false;
335       }
336       if (!CheckBoundsConflict(inner_bounds->min_height,
337                                outer_bounds->min_height,
338                                "minHeight",
339                                error)) {
340         return false;
341       }
342       if (!CheckBoundsConflict(inner_bounds->max_width,
343                                outer_bounds->max_width,
344                                "maxWidth",
345                                error)) {
346         return false;
347       }
348       if (!CheckBoundsConflict(inner_bounds->max_height,
349                                outer_bounds->max_height,
350                                "maxHeight",
351                                error)) {
352         return false;
353       }
354     }
355
356     CopyBoundsSpec(inner_bounds, &(params->content_spec));
357     CopyBoundsSpec(outer_bounds, &(params->window_spec));
358   } else {
359     // Parse deprecated fields.
360     // Due to a bug in NativeAppWindow::GetFrameInsets() on Windows and ChromeOS
361     // the bounds set the position of the window and the size of the content.
362     // This will be preserved as apps may be relying on this behavior.
363
364     if (options.default_width.get())
365       params->content_spec.bounds.set_width(*options.default_width.get());
366     if (options.default_height.get())
367       params->content_spec.bounds.set_height(*options.default_height.get());
368     if (options.default_left.get())
369       params->window_spec.bounds.set_x(*options.default_left.get());
370     if (options.default_top.get())
371       params->window_spec.bounds.set_y(*options.default_top.get());
372
373     if (options.width.get())
374       params->content_spec.bounds.set_width(*options.width.get());
375     if (options.height.get())
376       params->content_spec.bounds.set_height(*options.height.get());
377     if (options.left.get())
378       params->window_spec.bounds.set_x(*options.left.get());
379     if (options.top.get())
380       params->window_spec.bounds.set_y(*options.top.get());
381
382     if (options.bounds.get()) {
383       app_window::ContentBounds* bounds = options.bounds.get();
384       if (bounds->width.get())
385         params->content_spec.bounds.set_width(*bounds->width.get());
386       if (bounds->height.get())
387         params->content_spec.bounds.set_height(*bounds->height.get());
388       if (bounds->left.get())
389         params->window_spec.bounds.set_x(*bounds->left.get());
390       if (bounds->top.get())
391         params->window_spec.bounds.set_y(*bounds->top.get());
392     }
393
394     gfx::Size& minimum_size = params->content_spec.minimum_size;
395     if (options.min_width.get())
396       minimum_size.set_width(*options.min_width);
397     if (options.min_height.get())
398       minimum_size.set_height(*options.min_height);
399     gfx::Size& maximum_size = params->content_spec.maximum_size;
400     if (options.max_width.get())
401       maximum_size.set_width(*options.max_width);
402     if (options.max_height.get())
403       maximum_size.set_height(*options.max_height);
404   }
405
406   return true;
407 }
408
409 AppWindow::Frame AppWindowCreateFunction::GetFrameFromString(
410     const std::string& frame_string) {
411    if (frame_string == kHtmlFrameOption &&
412        (GetExtension()->HasAPIPermission(APIPermission::kExperimental) ||
413         CommandLine::ForCurrentProcess()->HasSwitch(
414             switches::kEnableExperimentalExtensionApis))) {
415      inject_html_titlebar_ = true;
416      return AppWindow::FRAME_NONE;
417    }
418
419    if (frame_string == kNoneFrameOption)
420     return AppWindow::FRAME_NONE;
421
422   return AppWindow::FRAME_CHROME;
423 }
424
425 bool AppWindowCreateFunction::GetFrameOptions(
426     const app_window::CreateWindowOptions& options,
427     AppWindow::CreateParams* create_params) {
428   if (!options.frame)
429     return true;
430
431   DCHECK(options.frame->as_string || options.frame->as_frame_options);
432   if (options.frame->as_string) {
433     create_params->frame = GetFrameFromString(*options.frame->as_string);
434     return true;
435   }
436
437   if (options.frame->as_frame_options->type)
438     create_params->frame =
439         GetFrameFromString(*options.frame->as_frame_options->type);
440
441   if (options.frame->as_frame_options->color.get()) {
442     if (create_params->frame != AppWindow::FRAME_CHROME) {
443       error_ = app_window_constants::kColorWithFrameNone;
444       return false;
445     }
446
447     if (!image_util::ParseCSSColorString(
448             *options.frame->as_frame_options->color,
449             &create_params->active_frame_color)) {
450       error_ = app_window_constants::kInvalidColorSpecification;
451       return false;
452     }
453
454     create_params->has_frame_color = true;
455     create_params->inactive_frame_color = create_params->active_frame_color;
456
457     if (options.frame->as_frame_options->inactive_color.get()) {
458       if (!image_util::ParseCSSColorString(
459               *options.frame->as_frame_options->inactive_color,
460               &create_params->inactive_frame_color)) {
461         error_ = app_window_constants::kInvalidColorSpecification;
462         return false;
463       }
464     }
465
466     return true;
467   }
468
469   if (options.frame->as_frame_options->inactive_color.get()) {
470     error_ = app_window_constants::kInactiveColorWithoutColor;
471     return false;
472   }
473
474   return true;
475 }
476
477 }  // namespace extensions