• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
DevToolsRestorer(AppWindowCreateFunction * delayed_create_function,content::RenderViewHost * created_view)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>;
~DevToolsRestorer()84   ~DevToolsRestorer() {}
85 
LoadCompleted()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.
CheckBoundsConflict(const scoped_ptr<int> & inner_property,const scoped_ptr<int> & outer_property,const std::string & property_name,std::string * 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.
CopyBoundsSpec(const extensions::api::app_window::BoundsSpecification * input_spec,apps::AppWindow::BoundsSpecification * create_spec)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 
AppWindowCreateFunction()139 AppWindowCreateFunction::AppWindowCreateFunction()
140     : inject_html_titlebar_(false) {}
141 
SendDelayedResponse()142 void AppWindowCreateFunction::SendDelayedResponse() {
143   SendResponse(true);
144 }
145 
RunAsync()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 
GetBoundsSpec(const extensions::api::app_window::CreateWindowOptions & options,apps::AppWindow::CreateParams * params,std::string * error)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 
GetFrameFromString(const std::string & frame_string)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 
GetFrameOptions(const app_window::CreateWindowOptions & options,AppWindow::CreateParams * create_params)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