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_current_window_internal/app_current_window_internal_api.h"
6
7#include "base/command_line.h"
8#include "extensions/browser/app_window/app_window.h"
9#include "extensions/browser/app_window/app_window_client.h"
10#include "extensions/browser/app_window/app_window_registry.h"
11#include "extensions/browser/app_window/native_app_window.h"
12#include "extensions/browser/app_window/size_constraints.h"
13#include "extensions/common/api/app_current_window_internal.h"
14#include "extensions/common/features/simple_feature.h"
15#include "extensions/common/permissions/permissions_data.h"
16#include "extensions/common/switches.h"
17#include "third_party/skia/include/core/SkRegion.h"
18
19namespace app_current_window_internal =
20    extensions::core_api::app_current_window_internal;
21
22namespace Show = app_current_window_internal::Show;
23namespace SetBounds = app_current_window_internal::SetBounds;
24namespace SetSizeConstraints = app_current_window_internal::SetSizeConstraints;
25namespace SetIcon = app_current_window_internal::SetIcon;
26namespace SetBadgeIcon = app_current_window_internal::SetBadgeIcon;
27namespace SetShape = app_current_window_internal::SetShape;
28namespace SetAlwaysOnTop = app_current_window_internal::SetAlwaysOnTop;
29namespace SetVisibleOnAllWorkspaces =
30    app_current_window_internal::SetVisibleOnAllWorkspaces;
31
32using app_current_window_internal::Bounds;
33using app_current_window_internal::Region;
34using app_current_window_internal::RegionRect;
35using app_current_window_internal::SizeConstraints;
36
37namespace extensions {
38
39namespace {
40
41const char kNoAssociatedAppWindow[] =
42    "The context from which the function was called did not have an "
43    "associated app window.";
44
45const char kDevChannelOnly[] =
46    "This function is currently only available in the Dev channel.";
47
48const char kRequiresFramelessWindow[] =
49    "This function requires a frameless window (frame:none).";
50
51const char kAlwaysOnTopPermission[] =
52    "The \"app.window.alwaysOnTop\" permission is required.";
53
54const char kInvalidParameters[] = "Invalid parameters.";
55
56const int kUnboundedSize = SizeConstraints::kUnboundedSize;
57
58void GetBoundsFields(const Bounds& bounds_spec, gfx::Rect* bounds) {
59  if (bounds_spec.left)
60    bounds->set_x(*bounds_spec.left);
61  if (bounds_spec.top)
62    bounds->set_y(*bounds_spec.top);
63  if (bounds_spec.width)
64    bounds->set_width(*bounds_spec.width);
65  if (bounds_spec.height)
66    bounds->set_height(*bounds_spec.height);
67}
68
69// Copy the constraint value from the API to our internal representation of
70// content size constraints. A value of zero resets the constraints. The insets
71// are used to transform window constraints to content constraints.
72void GetConstraintWidth(const scoped_ptr<int>& width,
73                        const gfx::Insets& insets,
74                        gfx::Size* size) {
75  if (!width.get())
76    return;
77
78  size->set_width(*width > 0 ? std::max(0, *width - insets.width())
79                             : kUnboundedSize);
80}
81
82void GetConstraintHeight(const scoped_ptr<int>& height,
83                         const gfx::Insets& insets,
84                         gfx::Size* size) {
85  if (!height.get())
86    return;
87
88  size->set_height(*height > 0 ? std::max(0, *height - insets.height())
89                               : kUnboundedSize);
90}
91
92}  // namespace
93
94namespace bounds {
95
96enum BoundsType {
97  INNER_BOUNDS,
98  OUTER_BOUNDS,
99  DEPRECATED_BOUNDS,
100  INVALID_TYPE
101};
102
103const char kInnerBoundsType[] = "innerBounds";
104const char kOuterBoundsType[] = "outerBounds";
105const char kDeprecatedBoundsType[] = "bounds";
106
107BoundsType GetBoundsType(const std::string& type_as_string) {
108  if (type_as_string == kInnerBoundsType)
109    return INNER_BOUNDS;
110  else if (type_as_string == kOuterBoundsType)
111    return OUTER_BOUNDS;
112  else if (type_as_string == kDeprecatedBoundsType)
113    return DEPRECATED_BOUNDS;
114  else
115    return INVALID_TYPE;
116}
117
118}  // namespace bounds
119
120bool AppCurrentWindowInternalExtensionFunction::RunSync() {
121  AppWindowRegistry* registry = AppWindowRegistry::Get(browser_context());
122  DCHECK(registry);
123  content::RenderViewHost* rvh = render_view_host();
124  if (!rvh)
125    // No need to set an error, since we won't return to the caller anyway if
126    // there's no RVH.
127    return false;
128  AppWindow* window = registry->GetAppWindowForRenderViewHost(rvh);
129  if (!window) {
130    error_ = kNoAssociatedAppWindow;
131    return false;
132  }
133  return RunWithWindow(window);
134}
135
136bool AppCurrentWindowInternalFocusFunction::RunWithWindow(AppWindow* window) {
137  window->GetBaseWindow()->Activate();
138  return true;
139}
140
141bool AppCurrentWindowInternalFullscreenFunction::RunWithWindow(
142    AppWindow* window) {
143  window->Fullscreen();
144  return true;
145}
146
147bool AppCurrentWindowInternalMaximizeFunction::RunWithWindow(
148    AppWindow* window) {
149  window->Maximize();
150  return true;
151}
152
153bool AppCurrentWindowInternalMinimizeFunction::RunWithWindow(
154    AppWindow* window) {
155  window->Minimize();
156  return true;
157}
158
159bool AppCurrentWindowInternalRestoreFunction::RunWithWindow(AppWindow* window) {
160  window->Restore();
161  return true;
162}
163
164bool AppCurrentWindowInternalDrawAttentionFunction::RunWithWindow(
165    AppWindow* window) {
166  window->GetBaseWindow()->FlashFrame(true);
167  return true;
168}
169
170bool AppCurrentWindowInternalClearAttentionFunction::RunWithWindow(
171    AppWindow* window) {
172  window->GetBaseWindow()->FlashFrame(false);
173  return true;
174}
175
176bool AppCurrentWindowInternalShowFunction::RunWithWindow(AppWindow* window) {
177  scoped_ptr<Show::Params> params(Show::Params::Create(*args_));
178  CHECK(params.get());
179  if (params->focused && !*params->focused)
180    window->Show(AppWindow::SHOW_INACTIVE);
181  else
182    window->Show(AppWindow::SHOW_ACTIVE);
183  return true;
184}
185
186bool AppCurrentWindowInternalHideFunction::RunWithWindow(AppWindow* window) {
187  window->Hide();
188  return true;
189}
190
191bool AppCurrentWindowInternalSetBoundsFunction::RunWithWindow(
192    AppWindow* window) {
193  scoped_ptr<SetBounds::Params> params(SetBounds::Params::Create(*args_));
194  CHECK(params.get());
195
196  bounds::BoundsType bounds_type = bounds::GetBoundsType(params->bounds_type);
197  if (bounds_type == bounds::INVALID_TYPE) {
198    NOTREACHED();
199    error_ = kInvalidParameters;
200    return false;
201  }
202
203  // Start with the current bounds, and change any values that are specified in
204  // the incoming parameters.
205  gfx::Rect original_window_bounds = window->GetBaseWindow()->GetBounds();
206  gfx::Rect window_bounds = original_window_bounds;
207  gfx::Insets frame_insets = window->GetBaseWindow()->GetFrameInsets();
208  const Bounds& bounds_spec = params->bounds;
209
210  switch (bounds_type) {
211    case bounds::DEPRECATED_BOUNDS: {
212      // We need to maintain backcompatibility with a bug on Windows and
213      // ChromeOS, which sets the position of the window but the size of the
214      // content.
215      if (bounds_spec.left)
216        window_bounds.set_x(*bounds_spec.left);
217      if (bounds_spec.top)
218        window_bounds.set_y(*bounds_spec.top);
219      if (bounds_spec.width)
220        window_bounds.set_width(*bounds_spec.width + frame_insets.width());
221      if (bounds_spec.height)
222        window_bounds.set_height(*bounds_spec.height + frame_insets.height());
223      break;
224    }
225    case bounds::OUTER_BOUNDS: {
226      GetBoundsFields(bounds_spec, &window_bounds);
227      break;
228    }
229    case bounds::INNER_BOUNDS: {
230      window_bounds.Inset(frame_insets);
231      GetBoundsFields(bounds_spec, &window_bounds);
232      window_bounds.Inset(-frame_insets);
233      break;
234    }
235    default:
236      NOTREACHED();
237  }
238
239  if (original_window_bounds != window_bounds) {
240    if (original_window_bounds.size() != window_bounds.size()) {
241      SizeConstraints constraints(
242          SizeConstraints::AddFrameToConstraints(
243              window->GetBaseWindow()->GetContentMinimumSize(), frame_insets),
244          SizeConstraints::AddFrameToConstraints(
245              window->GetBaseWindow()->GetContentMaximumSize(), frame_insets));
246
247      window_bounds.set_size(constraints.ClampSize(window_bounds.size()));
248    }
249
250    window->GetBaseWindow()->SetBounds(window_bounds);
251  }
252
253  return true;
254}
255
256bool AppCurrentWindowInternalSetSizeConstraintsFunction::RunWithWindow(
257    AppWindow* window) {
258  scoped_ptr<SetSizeConstraints::Params> params(
259      SetSizeConstraints::Params::Create(*args_));
260  CHECK(params.get());
261
262  bounds::BoundsType bounds_type = bounds::GetBoundsType(params->bounds_type);
263  if (bounds_type != bounds::INNER_BOUNDS &&
264      bounds_type != bounds::OUTER_BOUNDS) {
265    NOTREACHED();
266    error_ = kInvalidParameters;
267    return false;
268  }
269
270  gfx::Size original_min_size =
271      window->GetBaseWindow()->GetContentMinimumSize();
272  gfx::Size original_max_size =
273      window->GetBaseWindow()->GetContentMaximumSize();
274  gfx::Size min_size = original_min_size;
275  gfx::Size max_size = original_max_size;
276  const app_current_window_internal::SizeConstraints& constraints =
277      params->constraints;
278
279  // Use the frame insets to convert window size constraints to content size
280  // constraints.
281  gfx::Insets insets;
282  if (bounds_type == bounds::OUTER_BOUNDS)
283    insets = window->GetBaseWindow()->GetFrameInsets();
284
285  GetConstraintWidth(constraints.min_width, insets, &min_size);
286  GetConstraintWidth(constraints.max_width, insets, &max_size);
287  GetConstraintHeight(constraints.min_height, insets, &min_size);
288  GetConstraintHeight(constraints.max_height, insets, &max_size);
289
290  if (min_size != original_min_size || max_size != original_max_size)
291    window->SetContentSizeConstraints(min_size, max_size);
292
293  return true;
294}
295
296bool AppCurrentWindowInternalSetIconFunction::RunWithWindow(AppWindow* window) {
297  if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev() &&
298      extension()->location() != extensions::Manifest::COMPONENT) {
299    error_ = kDevChannelOnly;
300    return false;
301  }
302
303  scoped_ptr<SetIcon::Params> params(SetIcon::Params::Create(*args_));
304  CHECK(params.get());
305  // The |icon_url| parameter may be a blob url (e.g. an image fetched with an
306  // XMLHttpRequest) or a resource url.
307  GURL url(params->icon_url);
308  if (!url.is_valid())
309    url = extension()->GetResourceURL(params->icon_url);
310
311  window->SetAppIconUrl(url);
312  return true;
313}
314
315bool AppCurrentWindowInternalSetBadgeIconFunction::RunWithWindow(
316    AppWindow* window) {
317  if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) {
318    error_ = kDevChannelOnly;
319    return false;
320  }
321
322  scoped_ptr<SetBadgeIcon::Params> params(SetBadgeIcon::Params::Create(*args_));
323  CHECK(params.get());
324  // The |icon_url| parameter may be a blob url (e.g. an image fetched with an
325  // XMLHttpRequest) or a resource url.
326  GURL url(params->icon_url);
327  if (!url.is_valid() && !params->icon_url.empty())
328    url = extension()->GetResourceURL(params->icon_url);
329
330  window->SetBadgeIconUrl(url);
331  return true;
332}
333
334bool AppCurrentWindowInternalClearBadgeFunction::RunWithWindow(
335    AppWindow* window) {
336  if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) {
337    error_ = kDevChannelOnly;
338    return false;
339  }
340
341  window->ClearBadge();
342  return true;
343}
344
345bool AppCurrentWindowInternalSetShapeFunction::RunWithWindow(
346    AppWindow* window) {
347
348  if (!window->GetBaseWindow()->IsFrameless()) {
349    error_ = kRequiresFramelessWindow;
350    return false;
351  }
352
353  scoped_ptr<SetShape::Params> params(
354      SetShape::Params::Create(*args_));
355  const Region& shape = params->region;
356
357  // Build a region from the supplied list of rects.
358  // If |rects| is missing, then the input region is removed. This clears the
359  // input region so that the entire window accepts input events.
360  // To specify an empty input region (so the window ignores all input),
361  // |rects| should be an empty list.
362  scoped_ptr<SkRegion> region(new SkRegion);
363  if (shape.rects) {
364    for (std::vector<linked_ptr<RegionRect> >::const_iterator i =
365             shape.rects->begin();
366         i != shape.rects->end();
367         ++i) {
368      const RegionRect& inputRect = **i;
369      int32_t x = inputRect.left;
370      int32_t y = inputRect.top;
371      int32_t width = inputRect.width;
372      int32_t height = inputRect.height;
373
374      SkIRect rect = SkIRect::MakeXYWH(x, y, width, height);
375      region->op(rect, SkRegion::kUnion_Op);
376    }
377  } else {
378    region.reset(NULL);
379  }
380
381  window->UpdateShape(region.Pass());
382
383  return true;
384}
385
386bool AppCurrentWindowInternalSetAlwaysOnTopFunction::RunWithWindow(
387    AppWindow* window) {
388  if (!extension()->permissions_data()->HasAPIPermission(
389          extensions::APIPermission::kAlwaysOnTopWindows)) {
390    error_ = kAlwaysOnTopPermission;
391    return false;
392  }
393
394  scoped_ptr<SetAlwaysOnTop::Params> params(
395      SetAlwaysOnTop::Params::Create(*args_));
396  CHECK(params.get());
397  window->SetAlwaysOnTop(params->always_on_top);
398  return true;
399}
400
401bool AppCurrentWindowInternalSetVisibleOnAllWorkspacesFunction::RunWithWindow(
402    AppWindow* window) {
403  if (AppWindowClient::Get()->IsCurrentChannelOlderThanDev()) {
404    error_ = kDevChannelOnly;
405    return false;
406  }
407
408  scoped_ptr<SetVisibleOnAllWorkspaces::Params> params(
409      SetVisibleOnAllWorkspaces::Params::Create(*args_));
410  CHECK(params.get());
411  window->GetBaseWindow()->SetVisibleOnAllWorkspaces(params->always_visible);
412  return true;
413}
414
415}  // namespace extensions
416