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