Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / app_window / app_window_api.cc
index 7f0006d..0f578ac 100644 (file)
@@ -7,15 +7,15 @@
 #include "apps/app_window.h"
 #include "apps/app_window_contents.h"
 #include "apps/app_window_registry.h"
+#include "apps/ui/apps_client.h"
 #include "apps/ui/native_app_window.h"
 #include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
 #include "base/time/time.h"
 #include "base/values.h"
-#include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/devtools/devtools_window.h"
-#include "chrome/browser/extensions/window_controller.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/apps/chrome_shell_window_delegate.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/api/app_window.h"
 #include "chrome/common/extensions/features/feature_channel.h"
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/url_constants.h"
 #include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/image_util.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/switches.h"
+#include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/gfx/rect.h"
 #include "url/gurl.h"
 
-#if defined(USE_ASH)
-#include "ash/shell.h"
-#include "ui/aura/root_window.h"
-#include "ui/aura/window.h"
-#endif
-
 using apps::AppWindow;
 
 namespace app_window = extensions::api::app_window;
@@ -46,9 +44,27 @@ namespace extensions {
 namespace app_window_constants {
 const char kInvalidWindowId[] =
     "The window id can not be more than 256 characters long.";
-}
+const char kInvalidColorSpecification[] =
+    "The color specification could not be parsed.";
+const char kColorWithFrameNone[] = "Windows with no frame cannot have a color.";
+const char kInactiveColorWithoutColor[] =
+    "frame.inactiveColor must be used with frame.color.";
+const char kConflictingBoundsOptions[] =
+    "The $1 property cannot be specified for both inner and outer bounds.";
+const char kAlwaysOnTopPermission[] =
+    "The \"app.window.alwaysOnTop\" permission is required.";
+const char kInvalidUrlParameter[] =
+    "The URL used for window creation must be local for security reasons.";
+const char kAlphaEnabledWrongChannel[] =
+    "The alphaEnabled option requires dev channel or newer.";
+const char kAlphaEnabledMissingPermission[] =
+    "The alphaEnabled option requires app.window.alpha permission.";
+const char kAlphaEnabledNeedsFrameNone[] =
+    "The alphaEnabled option can only be used with \"frame: 'none'\".";
+}  // namespace app_window_constants
 
 const char kNoneFrameOption[] = "none";
+  // TODO(benwells): Remove HTML titlebar injection.
 const char kHtmlFrameOption[] = "experimental-html";
 
 namespace {
@@ -59,13 +75,12 @@ namespace {
 class DevToolsRestorer : public base::RefCounted<DevToolsRestorer> {
  public:
   DevToolsRestorer(AppWindowCreateFunction* delayed_create_function,
-                   content::RenderViewHost* created_view)
+                   content::WebContents* web_contents)
       : delayed_create_function_(delayed_create_function) {
     AddRef();  // Balanced in LoadCompleted.
-    DevToolsWindow* devtools_window =
-        DevToolsWindow::OpenDevToolsWindow(
-            created_view,
-            DevToolsToggleAction::ShowConsole());
+    DevToolsWindow* devtools_window = DevToolsWindow::OpenDevToolsWindow(
+        web_contents,
+        DevToolsToggleAction::ShowConsole());
     devtools_window->SetLoadCompletedCallback(
         base::Bind(&DevToolsRestorer::LoadCompleted, this));
   }
@@ -82,13 +97,59 @@ class DevToolsRestorer : public base::RefCounted<DevToolsRestorer> {
   scoped_refptr<AppWindowCreateFunction> delayed_create_function_;
 };
 
+// If the same property is specified for the inner and outer bounds, raise an
+// error.
+bool CheckBoundsConflict(const scoped_ptr<int>& inner_property,
+                         const scoped_ptr<int>& outer_property,
+                         const std::string& property_name,
+                         std::string* error) {
+  if (inner_property.get() && outer_property.get()) {
+    std::vector<std::string> subst;
+    subst.push_back(property_name);
+    *error = ReplaceStringPlaceholders(
+        app_window_constants::kConflictingBoundsOptions, subst, NULL);
+    return false;
+  }
+
+  return true;
+}
+
+// Copy over the bounds specification properties from the API to the
+// AppWindow::CreateParams.
+void CopyBoundsSpec(
+    const extensions::api::app_window::BoundsSpecification* input_spec,
+    apps::AppWindow::BoundsSpecification* create_spec) {
+  if (!input_spec)
+    return;
+
+  if (input_spec->left.get())
+    create_spec->bounds.set_x(*input_spec->left);
+  if (input_spec->top.get())
+    create_spec->bounds.set_y(*input_spec->top);
+  if (input_spec->width.get())
+    create_spec->bounds.set_width(*input_spec->width);
+  if (input_spec->height.get())
+    create_spec->bounds.set_height(*input_spec->height);
+  if (input_spec->min_width.get())
+    create_spec->minimum_size.set_width(*input_spec->min_width);
+  if (input_spec->min_height.get())
+    create_spec->minimum_size.set_height(*input_spec->min_height);
+  if (input_spec->max_width.get())
+    create_spec->maximum_size.set_width(*input_spec->max_width);
+  if (input_spec->max_height.get())
+    create_spec->maximum_size.set_height(*input_spec->max_height);
+}
+
 }  // namespace
 
+AppWindowCreateFunction::AppWindowCreateFunction()
+    : inject_html_titlebar_(false) {}
+
 void AppWindowCreateFunction::SendDelayedResponse() {
   SendResponse(true);
 }
 
-bool AppWindowCreateFunction::RunImpl() {
+bool AppWindowCreateFunction::RunAsync() {
   // Don't create app window if the system is shutting down.
   if (extensions::ExtensionsBrowserClient::Get()->IsShuttingDown())
     return false;
@@ -96,17 +157,20 @@ bool AppWindowCreateFunction::RunImpl() {
   scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
-  GURL url = GetExtension()->GetResourceURL(params->url);
+  GURL url = extension()->GetResourceURL(params->url);
   // Allow absolute URLs for component apps, otherwise prepend the extension
   // path.
-  if (GetExtension()->location() == extensions::Manifest::COMPONENT) {
-    GURL absolute = GURL(params->url);
-    if (absolute.has_scheme())
+  GURL absolute = GURL(params->url);
+  if (absolute.has_scheme()) {
+    if (extension()->location() == extensions::Manifest::COMPONENT) {
       url = absolute;
+    } else {
+      // Show error when url passed isn't local.
+      error_ = app_window_constants::kInvalidUrlParameter;
+      return false;
+    }
   }
 
-  bool inject_html_titlebar = false;
-
   // TODO(jeremya): figure out a way to pass the opening WebContents through to
   // AppWindow::Create so we can set the opener at create time rather than
   // with a hack in AppWindowCustomBindings::GetView().
@@ -131,9 +195,9 @@ bool AppWindowCreateFunction::RunImpl() {
       }
 
       if (!options->singleton || *options->singleton) {
-        AppWindow* window = apps::AppWindowRegistry::Get(GetProfile())
+        AppWindow* window = apps::AppWindowRegistry::Get(browser_context())
                                 ->GetAppWindowForAppAndKey(
-                                      extension_id(), create_params.window_key);
+                                    extension_id(), create_params.window_key);
         if (window) {
           content::RenderViewHost* created_view =
               window->web_contents()->GetRenderViewHost();
@@ -143,15 +207,18 @@ bool AppWindowCreateFunction::RunImpl() {
             view_id = created_view->GetRoutingID();
           }
 
-          if (options->focused.get() && !*options->focused.get())
-            window->Show(AppWindow::SHOW_INACTIVE);
-          else
-            window->Show(AppWindow::SHOW_ACTIVE);
+          if (options->hidden.get() && !*options->hidden.get()) {
+            if (options->focused.get() && !*options->focused.get())
+              window->Show(AppWindow::SHOW_INACTIVE);
+            else
+              window->Show(AppWindow::SHOW_ACTIVE);
+          }
 
           base::DictionaryValue* result = new base::DictionaryValue;
           result->Set("viewId", new base::FundamentalValue(view_id));
           window->GetSerializedState(result);
           result->SetBoolean("existingWindow", true);
+          // TODO(benwells): Remove HTML titlebar injection.
           result->SetBoolean("injectTitlebar", false);
           SetResult(result);
           SendResponse(true);
@@ -160,87 +227,70 @@ bool AppWindowCreateFunction::RunImpl() {
       }
     }
 
-    // TODO(jeremya): remove these, since they do the same thing as
-    // left/top/width/height.
-    if (options->default_width.get())
-      create_params.bounds.set_width(*options->default_width.get());
-    if (options->default_height.get())
-      create_params.bounds.set_height(*options->default_height.get());
-    if (options->default_left.get())
-      create_params.bounds.set_x(*options->default_left.get());
-    if (options->default_top.get())
-      create_params.bounds.set_y(*options->default_top.get());
-
-    if (options->width.get())
-      create_params.bounds.set_width(*options->width.get());
-    if (options->height.get())
-      create_params.bounds.set_height(*options->height.get());
-    if (options->left.get())
-      create_params.bounds.set_x(*options->left.get());
-    if (options->top.get())
-      create_params.bounds.set_y(*options->top.get());
-
-    if (options->bounds.get()) {
-      app_window::Bounds* bounds = options->bounds.get();
-      if (bounds->width.get())
-        create_params.bounds.set_width(*bounds->width.get());
-      if (bounds->height.get())
-        create_params.bounds.set_height(*bounds->height.get());
-      if (bounds->left.get())
-        create_params.bounds.set_x(*bounds->left.get());
-      if (bounds->top.get())
-        create_params.bounds.set_y(*bounds->top.get());
-    }
+    if (!GetBoundsSpec(*options, &create_params, &error_))
+      return false;
 
     if (GetCurrentChannel() <= chrome::VersionInfo::CHANNEL_DEV ||
-        GetExtension()->location() == extensions::Manifest::COMPONENT) {
+        extension()->location() == extensions::Manifest::COMPONENT) {
       if (options->type == extensions::api::app_window::WINDOW_TYPE_PANEL) {
         create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
       }
     }
 
-    if (options->frame.get()) {
-      if (*options->frame == kHtmlFrameOption &&
-          (GetExtension()->HasAPIPermission(APIPermission::kExperimental) ||
-           CommandLine::ForCurrentProcess()->HasSwitch(
-               switches::kEnableExperimentalExtensionApis))) {
-        create_params.frame = AppWindow::FRAME_NONE;
-        inject_html_titlebar = true;
-      } else if (*options->frame == kNoneFrameOption) {
-        create_params.frame = AppWindow::FRAME_NONE;
-      } else {
-        create_params.frame = AppWindow::FRAME_CHROME;
+    if (!GetFrameOptions(*options, &create_params))
+      return false;
+
+    if (options->alpha_enabled.get()) {
+      const char* whitelist[] = {
+        "0F42756099D914A026DADFA182871C015735DD95",  // http://crbug.com/323773
+        "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7",
+        "E7E2461CE072DF036CF9592740196159E2D7C089",  // http://crbug.com/356200
+        "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3",
+        "312745D9BF916161191143F6490085EEA0434997",
+        "53041A2FA309EECED01FFC751E7399186E860B2C"
+      };
+      if (GetCurrentChannel() > chrome::VersionInfo::CHANNEL_DEV &&
+          !extensions::SimpleFeature::IsIdInList(
+              extension_id(),
+              std::set<std::string>(whitelist,
+                                    whitelist + arraysize(whitelist)))) {
+        error_ = app_window_constants::kAlphaEnabledWrongChannel;
+        return false;
       }
+      if (!extension()->permissions_data()->HasAPIPermission(
+              APIPermission::kAlphaEnabled)) {
+        error_ = app_window_constants::kAlphaEnabledMissingPermission;
+        return false;
+      }
+      if (create_params.frame != AppWindow::FRAME_NONE) {
+        error_ = app_window_constants::kAlphaEnabledNeedsFrameNone;
+        return false;
+      }
+#if defined(USE_AURA)
+      create_params.alpha_enabled = *options->alpha_enabled;
+#else
+      // Transparency is only supported on Aura.
+      // Fallback to creating an opaque window (by ignoring alphaEnabled).
+#endif
     }
 
-    if (options->transparent_background.get() &&
-        (GetExtension()->HasAPIPermission(APIPermission::kExperimental) ||
-         CommandLine::ForCurrentProcess()->HasSwitch(
-             switches::kEnableExperimentalExtensionApis))) {
-      create_params.transparent_background = *options->transparent_background;
-    }
-
-    gfx::Size& minimum_size = create_params.minimum_size;
-    if (options->min_width.get())
-      minimum_size.set_width(*options->min_width);
-    if (options->min_height.get())
-      minimum_size.set_height(*options->min_height);
-    gfx::Size& maximum_size = create_params.maximum_size;
-    if (options->max_width.get())
-      maximum_size.set_width(*options->max_width);
-    if (options->max_height.get())
-      maximum_size.set_height(*options->max_height);
-
     if (options->hidden.get())
       create_params.hidden = *options->hidden.get();
 
     if (options->resizable.get())
       create_params.resizable = *options->resizable.get();
 
-    if (options->always_on_top.get() &&
-        GetExtension()->HasAPIPermission(APIPermission::kAlwaysOnTopWindows))
+    if (options->always_on_top.get()) {
       create_params.always_on_top = *options->always_on_top.get();
 
+      if (create_params.always_on_top &&
+          !extension()->permissions_data()->HasAPIPermission(
+              APIPermission::kAlwaysOnTopWindows)) {
+        error_ = app_window_constants::kAlwaysOnTopPermission;
+        return false;
+      }
+    }
+
     if (options->focused.get())
       create_params.focused = *options->focused.get();
 
@@ -265,12 +315,12 @@ bool AppWindowCreateFunction::RunImpl() {
   create_params.creator_process_id =
       render_view_host_->GetProcess()->GetID();
 
-  AppWindow* app_window = new AppWindow(
-      GetProfile(), new ChromeShellWindowDelegate(), GetExtension());
+  AppWindow* app_window =
+      apps::AppsClient::Get()->CreateAppWindow(browser_context(), extension());
   app_window->Init(
       url, new apps::AppWindowContentsImpl(app_window), create_params);
 
-  if (chrome::IsRunningInForcedAppMode())
+  if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode())
     app_window->ForcedFullscreen();
 
   content::RenderViewHost* created_view =
@@ -282,18 +332,201 @@ bool AppWindowCreateFunction::RunImpl() {
   base::DictionaryValue* result = new base::DictionaryValue;
   result->Set("viewId", new base::FundamentalValue(view_id));
   result->Set("injectTitlebar",
-      new base::FundamentalValue(inject_html_titlebar));
+      new base::FundamentalValue(inject_html_titlebar_));
   result->Set("id", new base::StringValue(app_window->window_key()));
   app_window->GetSerializedState(result);
   SetResult(result);
 
-  if (apps::AppWindowRegistry::Get(GetProfile())
+  if (apps::AppWindowRegistry::Get(browser_context())
           ->HadDevToolsAttached(created_view)) {
-    new DevToolsRestorer(this, created_view);
+    new DevToolsRestorer(this, app_window->web_contents());
     return true;
   }
 
   SendResponse(true);
+  app_window->WindowEventsReady();
+
+  return true;
+}
+
+bool AppWindowCreateFunction::GetBoundsSpec(
+    const extensions::api::app_window::CreateWindowOptions& options,
+    apps::AppWindow::CreateParams* params,
+    std::string* error) {
+  DCHECK(params);
+  DCHECK(error);
+
+  if (options.inner_bounds.get() || options.outer_bounds.get()) {
+    // Parse the inner and outer bounds specifications. If developers use the
+    // new API, the deprecated fields will be ignored - do not attempt to merge
+    // them.
+
+    const extensions::api::app_window::BoundsSpecification* inner_bounds =
+        options.inner_bounds.get();
+    const extensions::api::app_window::BoundsSpecification* outer_bounds =
+        options.outer_bounds.get();
+    if (inner_bounds && outer_bounds) {
+      if (!CheckBoundsConflict(
+               inner_bounds->left, outer_bounds->left, "left", error)) {
+        return false;
+      }
+      if (!CheckBoundsConflict(
+               inner_bounds->top, outer_bounds->top, "top", error)) {
+        return false;
+      }
+      if (!CheckBoundsConflict(
+               inner_bounds->width, outer_bounds->width, "width", error)) {
+        return false;
+      }
+      if (!CheckBoundsConflict(
+               inner_bounds->height, outer_bounds->height, "height", error)) {
+        return false;
+      }
+      if (!CheckBoundsConflict(inner_bounds->min_width,
+                               outer_bounds->min_width,
+                               "minWidth",
+                               error)) {
+        return false;
+      }
+      if (!CheckBoundsConflict(inner_bounds->min_height,
+                               outer_bounds->min_height,
+                               "minHeight",
+                               error)) {
+        return false;
+      }
+      if (!CheckBoundsConflict(inner_bounds->max_width,
+                               outer_bounds->max_width,
+                               "maxWidth",
+                               error)) {
+        return false;
+      }
+      if (!CheckBoundsConflict(inner_bounds->max_height,
+                               outer_bounds->max_height,
+                               "maxHeight",
+                               error)) {
+        return false;
+      }
+    }
+
+    CopyBoundsSpec(inner_bounds, &(params->content_spec));
+    CopyBoundsSpec(outer_bounds, &(params->window_spec));
+  } else {
+    // Parse deprecated fields.
+    // Due to a bug in NativeAppWindow::GetFrameInsets() on Windows and ChromeOS
+    // the bounds set the position of the window and the size of the content.
+    // This will be preserved as apps may be relying on this behavior.
+
+    if (options.default_width.get())
+      params->content_spec.bounds.set_width(*options.default_width.get());
+    if (options.default_height.get())
+      params->content_spec.bounds.set_height(*options.default_height.get());
+    if (options.default_left.get())
+      params->window_spec.bounds.set_x(*options.default_left.get());
+    if (options.default_top.get())
+      params->window_spec.bounds.set_y(*options.default_top.get());
+
+    if (options.width.get())
+      params->content_spec.bounds.set_width(*options.width.get());
+    if (options.height.get())
+      params->content_spec.bounds.set_height(*options.height.get());
+    if (options.left.get())
+      params->window_spec.bounds.set_x(*options.left.get());
+    if (options.top.get())
+      params->window_spec.bounds.set_y(*options.top.get());
+
+    if (options.bounds.get()) {
+      app_window::ContentBounds* bounds = options.bounds.get();
+      if (bounds->width.get())
+        params->content_spec.bounds.set_width(*bounds->width.get());
+      if (bounds->height.get())
+        params->content_spec.bounds.set_height(*bounds->height.get());
+      if (bounds->left.get())
+        params->window_spec.bounds.set_x(*bounds->left.get());
+      if (bounds->top.get())
+        params->window_spec.bounds.set_y(*bounds->top.get());
+    }
+
+    gfx::Size& minimum_size = params->content_spec.minimum_size;
+    if (options.min_width.get())
+      minimum_size.set_width(*options.min_width);
+    if (options.min_height.get())
+      minimum_size.set_height(*options.min_height);
+    gfx::Size& maximum_size = params->content_spec.maximum_size;
+    if (options.max_width.get())
+      maximum_size.set_width(*options.max_width);
+    if (options.max_height.get())
+      maximum_size.set_height(*options.max_height);
+  }
+
+  return true;
+}
+
+AppWindow::Frame AppWindowCreateFunction::GetFrameFromString(
+    const std::string& frame_string) {
+  if (frame_string == kHtmlFrameOption &&
+      (extension()->permissions_data()->HasAPIPermission(
+           APIPermission::kExperimental) ||
+       CommandLine::ForCurrentProcess()->HasSwitch(
+           switches::kEnableExperimentalExtensionApis))) {
+     inject_html_titlebar_ = true;
+     return AppWindow::FRAME_NONE;
+   }
+
+   if (frame_string == kNoneFrameOption)
+    return AppWindow::FRAME_NONE;
+
+  return AppWindow::FRAME_CHROME;
+}
+
+bool AppWindowCreateFunction::GetFrameOptions(
+    const app_window::CreateWindowOptions& options,
+    AppWindow::CreateParams* create_params) {
+  if (!options.frame)
+    return true;
+
+  DCHECK(options.frame->as_string || options.frame->as_frame_options);
+  if (options.frame->as_string) {
+    create_params->frame = GetFrameFromString(*options.frame->as_string);
+    return true;
+  }
+
+  if (options.frame->as_frame_options->type)
+    create_params->frame =
+        GetFrameFromString(*options.frame->as_frame_options->type);
+
+  if (options.frame->as_frame_options->color.get()) {
+    if (create_params->frame != AppWindow::FRAME_CHROME) {
+      error_ = app_window_constants::kColorWithFrameNone;
+      return false;
+    }
+
+    if (!image_util::ParseCSSColorString(
+            *options.frame->as_frame_options->color,
+            &create_params->active_frame_color)) {
+      error_ = app_window_constants::kInvalidColorSpecification;
+      return false;
+    }
+
+    create_params->has_frame_color = true;
+    create_params->inactive_frame_color = create_params->active_frame_color;
+
+    if (options.frame->as_frame_options->inactive_color.get()) {
+      if (!image_util::ParseCSSColorString(
+              *options.frame->as_frame_options->inactive_color,
+              &create_params->inactive_frame_color)) {
+        error_ = app_window_constants::kInvalidColorSpecification;
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  if (options.frame->as_frame_options->inactive_color.get()) {
+    error_ = app_window_constants::kInactiveColorWithoutColor;
+    return false;
+  }
+
   return true;
 }