summaryrefslogtreecommitdiff
path: root/chromium/extensions/browser/api/app_window/app_window_api.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/extensions/browser/api/app_window/app_window_api.cc')
-rw-r--r--chromium/extensions/browser/api/app_window/app_window_api.cc558
1 files changed, 558 insertions, 0 deletions
diff --git a/chromium/extensions/browser/api/app_window/app_window_api.cc b/chromium/extensions/browser/api/app_window/app_window_api.cc
new file mode 100644
index 00000000000..4f5ffa551fe
--- /dev/null
+++ b/chromium/extensions/browser/api/app_window/app_window_api.cc
@@ -0,0 +1,558 @@
+// 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 "extensions/browser/api/app_window/app_window_api.h"
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/browser_side_navigation_policy.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_client.h"
+#include "extensions/browser/app_window/app_window_contents.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/api/app_window.h"
+#include "extensions/common/features/simple_feature.h"
+#include "extensions/common/image_util.h"
+#include "extensions/common/manifest.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/geometry/rect.h"
+#include "url/gurl.h"
+
+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 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'\".";
+const char kImeWindowMissingPermission[] =
+ "Extensions require the \"app.window.ime\" permission to create windows.";
+const char kImeOptionIsNotSupported[] =
+ "The \"ime\" option is not supported for platform app.";
+#if !defined(OS_CHROMEOS)
+const char kImeWindowUnsupportedPlatform[] =
+ "The \"ime\" option can only be used on ChromeOS.";
+#else
+const char kImeWindowMustBeImeWindowOrPanel[] =
+ "IME extensions must create ime window ( with \"ime: true\" and "
+ "\"frame: 'none'\") or panel window (with \"type: panel\").";
+#endif
+} // namespace app_window_constants
+
+const char kNoneFrameOption[] = "none";
+
+namespace {
+
+// 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 = base::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 app_window::BoundsSpecification* input_spec,
+ 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() {}
+
+bool AppWindowCreateFunction::RunAsync() {
+ // Don't create app window if the system is shutting down.
+ if (ExtensionsBrowserClient::Get()->IsShuttingDown())
+ return false;
+
+ scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ GURL url = extension()->GetResourceURL(params->url);
+ // Allow absolute URLs for component apps, otherwise prepend the extension
+ // path.
+ // TODO(devlin): Investigate if this is still used. If not, kill it dead!
+ GURL absolute = GURL(params->url);
+ if (absolute.has_scheme()) {
+ if (extension()->location() == Manifest::COMPONENT) {
+ url = absolute;
+ } else {
+ // Show error when url passed isn't local.
+ error_ = app_window_constants::kInvalidUrlParameter;
+ return 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().
+ 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* existing_window =
+ AppWindowRegistry::Get(browser_context())
+ ->GetAppWindowForAppAndKey(extension_id(),
+ create_params.window_key);
+ if (existing_window) {
+ content::RenderFrameHost* existing_frame =
+ existing_window->web_contents()->GetMainFrame();
+ int frame_id = MSG_ROUTING_NONE;
+ if (render_frame_host()->GetProcess()->GetID() ==
+ existing_frame->GetProcess()->GetID()) {
+ frame_id = existing_frame->GetRoutingID();
+ }
+
+ if (!options->hidden.get() || !*options->hidden.get()) {
+ if (options->focused.get() && !*options->focused.get())
+ existing_window->Show(AppWindow::SHOW_INACTIVE);
+ else
+ existing_window->Show(AppWindow::SHOW_ACTIVE);
+ }
+
+ base::DictionaryValue* result = new base::DictionaryValue;
+ result->Set("frameId", new base::FundamentalValue(frame_id));
+ existing_window->GetSerializedState(result);
+ result->SetBoolean("existingWindow", true);
+ SetResult(result);
+ SendResponse(true);
+ return true;
+ }
+ }
+ }
+
+ if (!GetBoundsSpec(*options, &create_params, &error_))
+ return false;
+
+ if (options->type == app_window::WINDOW_TYPE_PANEL) {
+#if defined(OS_CHROMEOS)
+ // Panels for v2 apps are only supported on Chrome OS.
+ create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
+#else
+ WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING,
+ "Panels are not supported on this platform");
+#endif
+ }
+
+ if (!GetFrameOptions(*options, &create_params))
+ return false;
+
+ if (extension()->GetType() == Manifest::TYPE_EXTENSION) {
+ // Whitelisted IME extensions are allowed to use this API to create IME
+ // specific windows to show accented characters or suggestions.
+ if (!extension()->permissions_data()->HasAPIPermission(
+ APIPermission::kImeWindowEnabled)) {
+ error_ = app_window_constants::kImeWindowMissingPermission;
+ return false;
+ }
+
+#if !defined(OS_CHROMEOS)
+ // IME window is only supported on ChromeOS.
+ error_ = app_window_constants::kImeWindowUnsupportedPlatform;
+ return false;
+#else
+ // IME extensions must create ime window (with "ime: true" and
+ // "frame: none") or panel window (with "type: panel").
+ if (options->ime.get() && *options->ime.get() &&
+ create_params.frame == AppWindow::FRAME_NONE) {
+ create_params.is_ime_window = true;
+ } else if (options->type == app_window::WINDOW_TYPE_PANEL) {
+ create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
+ } else {
+ error_ = app_window_constants::kImeWindowMustBeImeWindowOrPanel;
+ return false;
+ }
+#endif // OS_CHROMEOS
+ } else {
+ if (options->ime.get()) {
+ error_ = app_window_constants::kImeOptionIsNotSupported;
+ return false;
+ }
+ }
+
+ if (options->alpha_enabled.get()) {
+ const char* const kWhitelist[] = {
+#if defined(OS_CHROMEOS)
+ "B58B99751225318C7EB8CF4688B5434661083E07", // http://crbug.com/410550
+ "06BE211D5F014BAB34BC22D9DDA09C63A81D828E", // http://crbug.com/425539
+ "F94EE6AB36D6C6588670B2B01EB65212D9C64E33",
+ "B9EF10DDFEA11EF77873CC5009809E5037FC4C7A", // http://crbug.com/435380
+#endif
+ "0F42756099D914A026DADFA182871C015735DD95", // http://crbug.com/323773
+ "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7",
+ "E7E2461CE072DF036CF9592740196159E2D7C089", // http://crbug.com/356200
+ "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3",
+ "312745D9BF916161191143F6490085EEA0434997",
+ "53041A2FA309EECED01FFC751E7399186E860B2C",
+ "A07A5B743CD82A1C2579DB77D353C98A23201EEF", // http://crbug.com/413748
+ "F16F23C83C5F6DAD9B65A120448B34056DD80691",
+ "0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B"
+ };
+ if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
+ !SimpleFeature::IsIdInArray(
+ extension_id(), kWhitelist, arraysize(kWhitelist))) {
+ 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->hidden.get())
+ create_params.hidden = *options->hidden.get();
+
+ if (options->resizable.get())
+ create_params.resizable = *options->resizable.get();
+
+ 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();
+
+ if (options->visible_on_all_workspaces.get()) {
+ create_params.visible_on_all_workspaces =
+ *options->visible_on_all_workspaces.get();
+ }
+
+ if (options->type != app_window::WINDOW_TYPE_PANEL) {
+ switch (options->state) {
+ case app_window::STATE_NONE:
+ case app_window::STATE_NORMAL:
+ break;
+ case app_window::STATE_FULLSCREEN:
+ create_params.state = ui::SHOW_STATE_FULLSCREEN;
+ break;
+ case app_window::STATE_MAXIMIZED:
+ create_params.state = ui::SHOW_STATE_MAXIMIZED;
+ break;
+ case app_window::STATE_MINIMIZED:
+ create_params.state = ui::SHOW_STATE_MINIMIZED;
+ break;
+ }
+ }
+ }
+
+ create_params.creator_process_id =
+ render_frame_host()->GetProcess()->GetID();
+
+ AppWindow* app_window =
+ AppWindowClient::Get()->CreateAppWindow(browser_context(), extension());
+ app_window->Init(url, new AppWindowContentsImpl(app_window),
+ render_frame_host(), create_params);
+
+ if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode() &&
+ !app_window->is_ime_window()) {
+ app_window->ForcedFullscreen();
+ }
+
+ content::RenderFrameHost* created_frame =
+ app_window->web_contents()->GetMainFrame();
+ int frame_id = MSG_ROUTING_NONE;
+ if (create_params.creator_process_id == created_frame->GetProcess()->GetID())
+ frame_id = created_frame->GetRoutingID();
+
+ base::DictionaryValue* result = new base::DictionaryValue;
+ result->Set("frameId", new base::FundamentalValue(frame_id));
+ result->Set("id", new base::StringValue(app_window->window_key()));
+ app_window->GetSerializedState(result);
+ SetResult(result);
+
+ if (AppWindowRegistry::Get(browser_context())
+ ->HadDevToolsAttached(app_window->web_contents())) {
+ AppWindowClient::Get()->OpenDevToolsWindow(
+ app_window->web_contents(),
+ base::Bind(&AppWindowCreateFunction::SendResponse, this, true));
+ return true;
+ }
+
+ // PlzNavigate: delay sending the response until the newly created window has
+ // been told to navigate, and blink has been correctly initialized in the
+ // renderer.
+ if (content::IsBrowserSideNavigationEnabled()) {
+ app_window->SetOnFirstCommitCallback(
+ base::Bind(&AppWindowCreateFunction::SendResponse, this, true));
+ return true;
+ }
+
+ SendResponse(true);
+ app_window->WindowEventsReady();
+
+ return true;
+}
+
+bool AppWindowCreateFunction::GetBoundsSpec(
+ const app_window::CreateWindowOptions& options,
+ 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 app_window::BoundsSpecification* inner_bounds =
+ options.inner_bounds.get();
+ const 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 == 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::ParseHexColorString(
+ *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::ParseHexColorString(
+ *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;
+}
+
+} // namespace extensions