Home | History | Annotate | Download | only in app_window
      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/permissions/permissions_data.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 namespace app_window = extensions::core_api::app_window;
     35 namespace Create = app_window::Create;
     36 
     37 namespace extensions {
     38 
     39 namespace app_window_constants {
     40 const char kInvalidWindowId[] =
     41     "The window id can not be more than 256 characters long.";
     42 const char kInvalidColorSpecification[] =
     43     "The color specification could not be parsed.";
     44 const char kColorWithFrameNone[] = "Windows with no frame cannot have a color.";
     45 const char kInactiveColorWithoutColor[] =
     46     "frame.inactiveColor must be used with frame.color.";
     47 const char kConflictingBoundsOptions[] =
     48     "The $1 property cannot be specified for both inner and outer bounds.";
     49 const char kAlwaysOnTopPermission[] =
     50     "The \"app.window.alwaysOnTop\" permission is required.";
     51 const char kInvalidUrlParameter[] =
     52     "The URL used for window creation must be local for security reasons.";
     53 const char kAlphaEnabledWrongChannel[] =
     54     "The alphaEnabled option requires dev channel or newer.";
     55 const char kAlphaEnabledMissingPermission[] =
     56     "The alphaEnabled option requires app.window.alpha permission.";
     57 const char kAlphaEnabledNeedsFrameNone[] =
     58     "The alphaEnabled option can only be used with \"frame: 'none'\".";
     59 const char kVisibleOnAllWorkspacesWrongChannel[] =
     60     "The visibleOnAllWorkspaces option requires dev channel or newer.";
     61 }  // namespace app_window_constants
     62 
     63 const char kNoneFrameOption[] = "none";
     64   // TODO(benwells): Remove HTML titlebar injection.
     65 const char kHtmlFrameOption[] = "experimental-html";
     66 
     67 namespace {
     68 
     69 // If the same property is specified for the inner and outer bounds, raise an
     70 // error.
     71 bool CheckBoundsConflict(const scoped_ptr<int>& inner_property,
     72                          const scoped_ptr<int>& outer_property,
     73                          const std::string& property_name,
     74                          std::string* error) {
     75   if (inner_property.get() && outer_property.get()) {
     76     std::vector<std::string> subst;
     77     subst.push_back(property_name);
     78     *error = ReplaceStringPlaceholders(
     79         app_window_constants::kConflictingBoundsOptions, subst, NULL);
     80     return false;
     81   }
     82 
     83   return true;
     84 }
     85 
     86 // Copy over the bounds specification properties from the API to the
     87 // AppWindow::CreateParams.
     88 void CopyBoundsSpec(const app_window::BoundsSpecification* input_spec,
     89                     AppWindow::BoundsSpecification* create_spec) {
     90   if (!input_spec)
     91     return;
     92 
     93   if (input_spec->left.get())
     94     create_spec->bounds.set_x(*input_spec->left);
     95   if (input_spec->top.get())
     96     create_spec->bounds.set_y(*input_spec->top);
     97   if (input_spec->width.get())
     98     create_spec->bounds.set_width(*input_spec->width);
     99   if (input_spec->height.get())
    100     create_spec->bounds.set_height(*input_spec->height);
    101   if (input_spec->min_width.get())
    102     create_spec->minimum_size.set_width(*input_spec->min_width);
    103   if (input_spec->min_height.get())
    104     create_spec->minimum_size.set_height(*input_spec->min_height);
    105   if (input_spec->max_width.get())
    106     create_spec->maximum_size.set_width(*input_spec->max_width);
    107   if (input_spec->max_height.get())
    108     create_spec->maximum_size.set_height(*input_spec->max_height);
    109 }
    110 
    111 }  // namespace
    112 
    113 AppWindowCreateFunction::AppWindowCreateFunction()
    114     : inject_html_titlebar_(false) {}
    115 
    116 bool AppWindowCreateFunction::RunAsync() {
    117   // Don't create app window if the system is shutting down.
    118   if (extensions::ExtensionsBrowserClient::Get()->IsShuttingDown())
    119     return false;
    120 
    121   scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
    122   EXTENSION_FUNCTION_VALIDATE(params.get());
    123 
    124   GURL url = extension()->GetResourceURL(params->url);
    125   // Allow absolute URLs for component apps, otherwise prepend the extension
    126   // path.
    127   GURL absolute = GURL(params->url);
    128   if (absolute.has_scheme()) {
    129     if (extension()->location() == extensions::Manifest::COMPONENT) {
    130       url = absolute;
    131     } else {
    132       // Show error when url passed isn't local.
    133       error_ = app_window_constants::kInvalidUrlParameter;
    134       return false;
    135     }
    136   }
    137 
    138   // TODO(jeremya): figure out a way to pass the opening WebContents through to
    139   // AppWindow::Create so we can set the opener at create time rather than
    140   // with a hack in AppWindowCustomBindings::GetView().
    141   AppWindow::CreateParams create_params;
    142   app_window::CreateWindowOptions* options = params->options.get();
    143   if (options) {
    144     if (options->id.get()) {
    145       // TODO(mek): use URL if no id specified?
    146       // Limit length of id to 256 characters.
    147       if (options->id->length() > 256) {
    148         error_ = app_window_constants::kInvalidWindowId;
    149         return false;
    150       }
    151 
    152       create_params.window_key = *options->id;
    153 
    154       if (options->singleton && *options->singleton == false) {
    155         WriteToConsole(
    156           content::CONSOLE_MESSAGE_LEVEL_WARNING,
    157           "The 'singleton' option in chrome.apps.window.create() is deprecated!"
    158           " Change your code to no longer rely on this.");
    159       }
    160 
    161       if (!options->singleton || *options->singleton) {
    162         AppWindow* window = AppWindowRegistry::Get(browser_context())
    163                                 ->GetAppWindowForAppAndKey(
    164                                     extension_id(), create_params.window_key);
    165         if (window) {
    166           content::RenderViewHost* created_view =
    167               window->web_contents()->GetRenderViewHost();
    168           int view_id = MSG_ROUTING_NONE;
    169           if (render_view_host_->GetProcess()->GetID() ==
    170               created_view->GetProcess()->GetID()) {
    171             view_id = created_view->GetRoutingID();
    172           }
    173 
    174           if (options->hidden.get() && !*options->hidden.get()) {
    175             if (options->focused.get() && !*options->focused.get())
    176               window->Show(AppWindow::SHOW_INACTIVE);
    177             else
    178               window->Show(AppWindow::SHOW_ACTIVE);
    179           }
    180 
    181           base::DictionaryValue* result = new base::DictionaryValue;
    182           result->Set("viewId", new base::FundamentalValue(view_id));
    183           window->GetSerializedState(result);
    184           result->SetBoolean("existingWindow", true);
    185           // TODO(benwells): Remove HTML titlebar injection.
    186           result->SetBoolean("injectTitlebar", false);
    187           SetResult(result);
    188           SendResponse(true);
    189           return true;
    190         }
    191       }
    192     }
    193 
    194     if (!GetBoundsSpec(*options, &create_params, &error_))
    195       return false;
    196 
    197     if (!AppWindowClient::Get()->IsCurrentChannelOlderThanDev() ||
    198         extension()->location() == extensions::Manifest::COMPONENT) {
    199       if (options->type == app_window::WINDOW_TYPE_PANEL) {
    200         create_params.window_type = AppWindow::WINDOW_TYPE_PANEL;
    201       }
    202     }
    203 
    204     if (!GetFrameOptions(*options, &create_params))
    205       return false;
    206 
    207     if (options->alpha_enabled.get()) {
    208       const char* whitelist[] = {
    209 #if defined(OS_CHROMEOS)
    210         "B58B99751225318C7EB8CF4688B5434661083E07",  // http://crbug.com/410550
    211 #endif
    212         "0F42756099D914A026DADFA182871C015735DD95",  // http://crbug.com/323773
    213         "2D22CDB6583FD0A13758AEBE8B15E45208B4E9A7",
    214         "E7E2461CE072DF036CF9592740196159E2D7C089",  // http://crbug.com/356200
    215         "A74A4D44C7CFCD8844830E6140C8D763E12DD8F3",
    216         "312745D9BF916161191143F6490085EEA0434997",
    217         "53041A2FA309EECED01FFC751E7399186E860B2C",
    218         "A07A5B743CD82A1C2579DB77D353C98A23201EEF",  // http://crbug.com/413748
    219         "F16F23C83C5F6DAD9B65A120448B34056DD80691",
    220         "0F585FB1D0FDFBEBCE1FEB5E9DFFB6DA476B8C9B"
    221       };
    222       if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
    223           !extensions::SimpleFeature::IsIdInList(
    224               extension_id(),
    225               std::set<std::string>(whitelist,
    226                                     whitelist + arraysize(whitelist)))) {
    227         error_ = app_window_constants::kAlphaEnabledWrongChannel;
    228         return false;
    229       }
    230       if (!extension()->permissions_data()->HasAPIPermission(
    231               APIPermission::kAlphaEnabled)) {
    232         error_ = app_window_constants::kAlphaEnabledMissingPermission;
    233         return false;
    234       }
    235       if (create_params.frame != AppWindow::FRAME_NONE) {
    236         error_ = app_window_constants::kAlphaEnabledNeedsFrameNone;
    237         return false;
    238       }
    239 #if defined(USE_AURA)
    240       create_params.alpha_enabled = *options->alpha_enabled;
    241 #else
    242       // Transparency is only supported on Aura.
    243       // Fallback to creating an opaque window (by ignoring alphaEnabled).
    244 #endif
    245     }
    246 
    247     if (options->hidden.get())
    248       create_params.hidden = *options->hidden.get();
    249 
    250     if (options->resizable.get())
    251       create_params.resizable = *options->resizable.get();
    252 
    253     if (options->always_on_top.get()) {
    254       create_params.always_on_top = *options->always_on_top.get();
    255 
    256       if (create_params.always_on_top &&
    257           !extension()->permissions_data()->HasAPIPermission(
    258               APIPermission::kAlwaysOnTopWindows)) {
    259         error_ = app_window_constants::kAlwaysOnTopPermission;
    260         return false;
    261       }
    262     }
    263 
    264     if (options->focused.get())
    265       create_params.focused = *options->focused.get();
    266 
    267     if (options->visible_on_all_workspaces.get()) {
    268       if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) {
    269         error_ = app_window_constants::kVisibleOnAllWorkspacesWrongChannel;
    270         return false;
    271       }
    272       create_params.visible_on_all_workspaces =
    273           *options->visible_on_all_workspaces.get();
    274     }
    275 
    276     if (options->type != app_window::WINDOW_TYPE_PANEL) {
    277       switch (options->state) {
    278         case app_window::STATE_NONE:
    279         case app_window::STATE_NORMAL:
    280           break;
    281         case app_window::STATE_FULLSCREEN:
    282           create_params.state = ui::SHOW_STATE_FULLSCREEN;
    283           break;
    284         case app_window::STATE_MAXIMIZED:
    285           create_params.state = ui::SHOW_STATE_MAXIMIZED;
    286           break;
    287         case app_window::STATE_MINIMIZED:
    288           create_params.state = ui::SHOW_STATE_MINIMIZED;
    289           break;
    290       }
    291     }
    292   }
    293 
    294   create_params.creator_process_id =
    295       render_view_host_->GetProcess()->GetID();
    296 
    297   AppWindow* app_window =
    298       AppWindowClient::Get()->CreateAppWindow(browser_context(), extension());
    299   app_window->Init(url, new AppWindowContentsImpl(app_window), create_params);
    300 
    301   if (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode())
    302     app_window->ForcedFullscreen();
    303 
    304   content::RenderViewHost* created_view =
    305       app_window->web_contents()->GetRenderViewHost();
    306   int view_id = MSG_ROUTING_NONE;
    307   if (create_params.creator_process_id == created_view->GetProcess()->GetID())
    308     view_id = created_view->GetRoutingID();
    309 
    310   base::DictionaryValue* result = new base::DictionaryValue;
    311   result->Set("viewId", new base::FundamentalValue(view_id));
    312   result->Set("injectTitlebar",
    313       new base::FundamentalValue(inject_html_titlebar_));
    314   result->Set("id", new base::StringValue(app_window->window_key()));
    315   app_window->GetSerializedState(result);
    316   SetResult(result);
    317 
    318   if (AppWindowRegistry::Get(browser_context())
    319           ->HadDevToolsAttached(created_view)) {
    320     AppWindowClient::Get()->OpenDevToolsWindow(
    321         app_window->web_contents(),
    322         base::Bind(&AppWindowCreateFunction::SendResponse, this, true));
    323     return true;
    324   }
    325 
    326   SendResponse(true);
    327   app_window->WindowEventsReady();
    328 
    329   return true;
    330 }
    331 
    332 bool AppWindowCreateFunction::GetBoundsSpec(
    333     const app_window::CreateWindowOptions& options,
    334     AppWindow::CreateParams* params,
    335     std::string* error) {
    336   DCHECK(params);
    337   DCHECK(error);
    338 
    339   if (options.inner_bounds.get() || options.outer_bounds.get()) {
    340     // Parse the inner and outer bounds specifications. If developers use the
    341     // new API, the deprecated fields will be ignored - do not attempt to merge
    342     // them.
    343 
    344     const app_window::BoundsSpecification* inner_bounds =
    345         options.inner_bounds.get();
    346     const app_window::BoundsSpecification* outer_bounds =
    347         options.outer_bounds.get();
    348     if (inner_bounds && outer_bounds) {
    349       if (!CheckBoundsConflict(
    350                inner_bounds->left, outer_bounds->left, "left", error)) {
    351         return false;
    352       }
    353       if (!CheckBoundsConflict(
    354                inner_bounds->top, outer_bounds->top, "top", error)) {
    355         return false;
    356       }
    357       if (!CheckBoundsConflict(
    358                inner_bounds->width, outer_bounds->width, "width", error)) {
    359         return false;
    360       }
    361       if (!CheckBoundsConflict(
    362                inner_bounds->height, outer_bounds->height, "height", error)) {
    363         return false;
    364       }
    365       if (!CheckBoundsConflict(inner_bounds->min_width,
    366                                outer_bounds->min_width,
    367                                "minWidth",
    368                                error)) {
    369         return false;
    370       }
    371       if (!CheckBoundsConflict(inner_bounds->min_height,
    372                                outer_bounds->min_height,
    373                                "minHeight",
    374                                error)) {
    375         return false;
    376       }
    377       if (!CheckBoundsConflict(inner_bounds->max_width,
    378                                outer_bounds->max_width,
    379                                "maxWidth",
    380                                error)) {
    381         return false;
    382       }
    383       if (!CheckBoundsConflict(inner_bounds->max_height,
    384                                outer_bounds->max_height,
    385                                "maxHeight",
    386                                error)) {
    387         return false;
    388       }
    389     }
    390 
    391     CopyBoundsSpec(inner_bounds, &(params->content_spec));
    392     CopyBoundsSpec(outer_bounds, &(params->window_spec));
    393   } else {
    394     // Parse deprecated fields.
    395     // Due to a bug in NativeAppWindow::GetFrameInsets() on Windows and ChromeOS
    396     // the bounds set the position of the window and the size of the content.
    397     // This will be preserved as apps may be relying on this behavior.
    398 
    399     if (options.default_width.get())
    400       params->content_spec.bounds.set_width(*options.default_width.get());
    401     if (options.default_height.get())
    402       params->content_spec.bounds.set_height(*options.default_height.get());
    403     if (options.default_left.get())
    404       params->window_spec.bounds.set_x(*options.default_left.get());
    405     if (options.default_top.get())
    406       params->window_spec.bounds.set_y(*options.default_top.get());
    407 
    408     if (options.width.get())
    409       params->content_spec.bounds.set_width(*options.width.get());
    410     if (options.height.get())
    411       params->content_spec.bounds.set_height(*options.height.get());
    412     if (options.left.get())
    413       params->window_spec.bounds.set_x(*options.left.get());
    414     if (options.top.get())
    415       params->window_spec.bounds.set_y(*options.top.get());
    416 
    417     if (options.bounds.get()) {
    418       app_window::ContentBounds* bounds = options.bounds.get();
    419       if (bounds->width.get())
    420         params->content_spec.bounds.set_width(*bounds->width.get());
    421       if (bounds->height.get())
    422         params->content_spec.bounds.set_height(*bounds->height.get());
    423       if (bounds->left.get())
    424         params->window_spec.bounds.set_x(*bounds->left.get());
    425       if (bounds->top.get())
    426         params->window_spec.bounds.set_y(*bounds->top.get());
    427     }
    428 
    429     gfx::Size& minimum_size = params->content_spec.minimum_size;
    430     if (options.min_width.get())
    431       minimum_size.set_width(*options.min_width);
    432     if (options.min_height.get())
    433       minimum_size.set_height(*options.min_height);
    434     gfx::Size& maximum_size = params->content_spec.maximum_size;
    435     if (options.max_width.get())
    436       maximum_size.set_width(*options.max_width);
    437     if (options.max_height.get())
    438       maximum_size.set_height(*options.max_height);
    439   }
    440 
    441   return true;
    442 }
    443 
    444 AppWindow::Frame AppWindowCreateFunction::GetFrameFromString(
    445     const std::string& frame_string) {
    446   if (frame_string == kHtmlFrameOption &&
    447       (extension()->permissions_data()->HasAPIPermission(
    448            APIPermission::kExperimental) ||
    449        CommandLine::ForCurrentProcess()->HasSwitch(
    450            switches::kEnableExperimentalExtensionApis))) {
    451      inject_html_titlebar_ = true;
    452      return AppWindow::FRAME_NONE;
    453    }
    454 
    455    if (frame_string == kNoneFrameOption)
    456     return AppWindow::FRAME_NONE;
    457 
    458   return AppWindow::FRAME_CHROME;
    459 }
    460 
    461 bool AppWindowCreateFunction::GetFrameOptions(
    462     const app_window::CreateWindowOptions& options,
    463     AppWindow::CreateParams* create_params) {
    464   if (!options.frame)
    465     return true;
    466 
    467   DCHECK(options.frame->as_string || options.frame->as_frame_options);
    468   if (options.frame->as_string) {
    469     create_params->frame = GetFrameFromString(*options.frame->as_string);
    470     return true;
    471   }
    472 
    473   if (options.frame->as_frame_options->type)
    474     create_params->frame =
    475         GetFrameFromString(*options.frame->as_frame_options->type);
    476 
    477   if (options.frame->as_frame_options->color.get()) {
    478     if (create_params->frame != AppWindow::FRAME_CHROME) {
    479       error_ = app_window_constants::kColorWithFrameNone;
    480       return false;
    481     }
    482 
    483     if (!image_util::ParseCSSColorString(
    484             *options.frame->as_frame_options->color,
    485             &create_params->active_frame_color)) {
    486       error_ = app_window_constants::kInvalidColorSpecification;
    487       return false;
    488     }
    489 
    490     create_params->has_frame_color = true;
    491     create_params->inactive_frame_color = create_params->active_frame_color;
    492 
    493     if (options.frame->as_frame_options->inactive_color.get()) {
    494       if (!image_util::ParseCSSColorString(
    495               *options.frame->as_frame_options->inactive_color,
    496               &create_params->inactive_frame_color)) {
    497         error_ = app_window_constants::kInvalidColorSpecification;
    498         return false;
    499       }
    500     }
    501 
    502     return true;
    503   }
    504 
    505   if (options.frame->as_frame_options->inactive_color.get()) {
    506     error_ = app_window_constants::kInactiveColorWithoutColor;
    507     return false;
    508   }
    509 
    510   return true;
    511 }
    512 
    513 }  // namespace extensions
    514