root/chrome/browser/extensions/api/app_window/app_window_api.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. LoadCompleted
  2. CheckBoundsConflict
  3. CopyBoundsSpec
  4. SendDelayedResponse
  5. RunImpl
  6. GetBoundsSpec
  7. GetFrameFromString
  8. GetFrameOptions
  9. UpdateFrameOptionsForChannel

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/api/app_window/app_window_api.h"

#include "apps/app_window.h"
#include "apps/app_window_contents.h"
#include "apps/app_window_registry.h"
#include "apps/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/devtools/devtools_window.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/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.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/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"

using apps::AppWindow;

namespace app_window = extensions::api::app_window;
namespace Create = app_window::Create;

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 kInvalidChannelForFrameOptions[] =
    "frameOptions is only available in dev channel.";
const char kColorWithFrameNone[] = "Windows with no frame cannot have a color.";
const char kInvalidChannelForBounds[] =
    "innerBounds and outerBounds are only available in dev channel.";
const char kConflictingBoundsOptions[] =
    "The $1 property cannot be specified for both inner and outer bounds.";
}  // namespace app_window_constants

const char kNoneFrameOption[] = "none";
  // TODO(benwells): Remove HTML titlebar injection.
const char kHtmlFrameOption[] = "experimental-html";

namespace {

// Opens an inspector window and delays the response to the
// AppWindowCreateFunction until the DevToolsWindow has finished loading, and is
// ready to stop on breakpoints in the callback.
class DevToolsRestorer : public base::RefCounted<DevToolsRestorer> {
 public:
  DevToolsRestorer(AppWindowCreateFunction* delayed_create_function,
                   content::RenderViewHost* created_view)
      : delayed_create_function_(delayed_create_function) {
    AddRef();  // Balanced in LoadCompleted.
    DevToolsWindow* devtools_window =
        DevToolsWindow::OpenDevToolsWindow(
            created_view,
            DevToolsToggleAction::ShowConsole());
    devtools_window->SetLoadCompletedCallback(
        base::Bind(&DevToolsRestorer::LoadCompleted, this));
  }

 private:
  friend class base::RefCounted<DevToolsRestorer>;
  ~DevToolsRestorer() {}

  void LoadCompleted() {
    delayed_create_function_->SendDelayedResponse();
    Release();
  }

  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() {
  // Don't create app window if the system is shutting down.
  if (extensions::ExtensionsBrowserClient::Get()->IsShuttingDown())
    return false;

  scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  GURL url = GetExtension()->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())
      url = absolute;
  }

  // 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().
  AppWindow::CreateParams create_params;
  app_window::CreateWindowOptions* options = params->options.get();
  if (options) {
    if (options->id.get()) {
      // TODO(mek): use URL if no id specified?
      // Limit length of id to 256 characters.
      if (options->id->length() > 256) {
        error_ = app_window_constants::kInvalidWindowId;
        return false;
      }

      create_params.window_key = *options->id;

      if (options->singleton && *options->singleton == false) {
        WriteToConsole(
          content::CONSOLE_MESSAGE_LEVEL_WARNING,
          "The 'singleton' option in chrome.apps.window.create() is deprecated!"
          " Change your code to no longer rely on this.");
      }

      if (!options->singleton || *options->singleton) {
        AppWindow* window = apps::AppWindowRegistry::Get(browser_context())
                                ->GetAppWindowForAppAndKey(
                                    extension_id(), create_params.window_key);
        if (window) {
          content::RenderViewHost* created_view =
              window->web_contents()->GetRenderViewHost();
          int view_id = MSG_ROUTING_NONE;
          if (render_view_host_->GetProcess()->GetID() ==
              created_view->GetProcess()->GetID()) {
            view_id = created_view->GetRoutingID();
          }

          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);
          return true;
        }
      }
    }

    if (!GetBoundsSpec(*options, &create_params, &error_))
      return false;

    if (GetCurrentChannel() <= chrome::VersionInfo::CHANNEL_DEV ||
        GetExtension()->location() == extensions::Manifest::COMPONENT) {
      if (options->type == extensions::api::app_window::WINDOW_TYPE_PANEL) {
        create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
      }
    }

    if (!GetFrameOptions(*options, &create_params))
      return false;

    if (options->transparent_background.get() &&
        (GetExtension()->HasAPIPermission(APIPermission::kExperimental) ||
         CommandLine::ForCurrentProcess()->HasSwitch(
             switches::kEnableExperimentalExtensionApis))) {
      create_params.transparent_background = *options->transparent_background;
    }

    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))
      create_params.always_on_top = *options->always_on_top.get();

    if (options->focused.get())
      create_params.focused = *options->focused.get();

    if (options->type != extensions::api::app_window::WINDOW_TYPE_PANEL) {
      switch (options->state) {
        case extensions::api::app_window::STATE_NONE:
        case extensions::api::app_window::STATE_NORMAL:
          break;
        case extensions::api::app_window::STATE_FULLSCREEN:
          create_params.state = ui::SHOW_STATE_FULLSCREEN;
          break;
        case extensions::api::app_window::STATE_MAXIMIZED:
          create_params.state = ui::SHOW_STATE_MAXIMIZED;
          break;
        case extensions::api::app_window::STATE_MINIMIZED:
          create_params.state = ui::SHOW_STATE_MINIMIZED;
          break;
      }
    }
  }

  UpdateFrameOptionsForChannel(&create_params);

  create_params.creator_process_id =
      render_view_host_->GetProcess()->GetID();

  AppWindow* app_window = apps::AppsClient::Get()->CreateAppWindow(
      browser_context(), GetExtension());
  app_window->Init(
      url, new apps::AppWindowContentsImpl(app_window), create_params);

  if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode())
    app_window->ForcedFullscreen();

  content::RenderViewHost* created_view =
      app_window->web_contents()->GetRenderViewHost();
  int view_id = MSG_ROUTING_NONE;
  if (create_params.creator_process_id == created_view->GetProcess()->GetID())
    view_id = created_view->GetRoutingID();

  base::DictionaryValue* result = new base::DictionaryValue;
  result->Set("viewId", new base::FundamentalValue(view_id));
  result->Set("injectTitlebar",
      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(browser_context())
          ->HadDevToolsAttached(created_view)) {
    new DevToolsRestorer(this, created_view);
    return true;
  }

  SendResponse(true);
  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.

    if (GetCurrentChannel() > chrome::VersionInfo::CHANNEL_DEV) {
      *error = app_window_constants::kInvalidChannelForBounds;
      return false;
    }

    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 &&
       (GetExtension()->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 (GetCurrentChannel() > chrome::VersionInfo::CHANNEL_DEV) {
    error_ = app_window_constants::kInvalidChannelForFrameOptions;
    return false;
  }

  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->frame_color)) {
      create_params->has_frame_color = true;
      return true;
    }

    error_ = app_window_constants::kInvalidColorSpecification;
    return false;
  }

  return true;
}

void AppWindowCreateFunction::UpdateFrameOptionsForChannel(
    apps::AppWindow::CreateParams* create_params) {
#if defined(OS_WIN)
  if (create_params->frame == AppWindow::FRAME_CHROME &&
      GetCurrentChannel() > chrome::VersionInfo::CHANNEL_DEV) {
    // If not on trunk or dev channel, always use the standard white frame.
    // TODO(benwells): Remove this code once we get agreement to use the new
    // native style frame.
    create_params->has_frame_color = true;
    create_params->frame_color = SK_ColorWHITE;
  }
#endif
}

}  // namespace extensions

/* [<][>][^][v][top][bottom][index][help] */