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_contents.h"
8#include "apps/shell_window.h"
9#include "apps/shell_window_registry.h"
10#include "apps/ui/native_app_window.h"
11#include "base/command_line.h"
12#include "base/time/time.h"
13#include "base/values.h"
14#include "chrome/browser/app_mode/app_mode_utils.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/devtools/devtools_window.h"
17#include "chrome/browser/extensions/window_controller.h"
18#include "chrome/browser/ui/apps/chrome_shell_window_delegate.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/common/switches.h"
28#include "ui/base/ui_base_types.h"
29#include "ui/gfx/rect.h"
30#include "url/gurl.h"
31
32#if defined(USE_ASH)
33#include "ash/shell.h"
34#include "ui/aura/root_window.h"
35#include "ui/aura/window.h"
36#endif
37
38using apps::ShellWindow;
39
40namespace app_window = extensions::api::app_window;
41namespace Create = app_window::Create;
42
43namespace extensions {
44
45namespace app_window_constants {
46const char kInvalidWindowId[] =
47    "The window id can not be more than 256 characters long.";
48}
49
50const char kNoneFrameOption[] = "none";
51const char kHtmlFrameOption[] = "experimental-html";
52
53namespace {
54
55const int kUnboundedSize = apps::ShellWindow::SizeConstraints::kUnboundedSize;
56
57// Opens an inspector window and delays the response to the
58// AppWindowCreateFunction until the DevToolsWindow has finished loading, and is
59// ready to stop on breakpoints in the callback.
60class DevToolsRestorer : public content::NotificationObserver {
61 public:
62  DevToolsRestorer(AppWindowCreateFunction* delayed_create_function,
63                   content::RenderViewHost* created_view)
64      : delayed_create_function_(delayed_create_function) {
65    DevToolsWindow* devtools_window =
66        DevToolsWindow::ToggleDevToolsWindow(
67            created_view,
68            true /* force_open */,
69            DevToolsToggleAction::ShowConsole());
70
71    registrar_.Add(
72        this,
73        content::NOTIFICATION_LOAD_STOP,
74        content::Source<content::NavigationController>(
75            &devtools_window->web_contents()->GetController()));
76  }
77
78 protected:
79  // content::NotificationObserver:
80  virtual void Observe(int type,
81                       const content::NotificationSource& source,
82                       const content::NotificationDetails& details) OVERRIDE {
83    DCHECK(type == content::NOTIFICATION_LOAD_STOP);
84    delayed_create_function_->SendDelayedResponse();
85    delete this;
86  }
87
88 private:
89  scoped_refptr<AppWindowCreateFunction> delayed_create_function_;
90  content::NotificationRegistrar registrar_;
91};
92
93void SetCreateResultFromShellWindow(ShellWindow* window,
94                                    base::DictionaryValue* result) {
95  result->SetBoolean("fullscreen", window->GetBaseWindow()->IsFullscreen());
96  result->SetBoolean("minimized", window->GetBaseWindow()->IsMinimized());
97  result->SetBoolean("maximized", window->GetBaseWindow()->IsMaximized());
98  result->SetBoolean("alwaysOnTop", window->IsAlwaysOnTop());
99  base::DictionaryValue* boundsValue = new base::DictionaryValue();
100  gfx::Rect bounds = window->GetClientBounds();
101  boundsValue->SetInteger("left", bounds.x());
102  boundsValue->SetInteger("top", bounds.y());
103  boundsValue->SetInteger("width", bounds.width());
104  boundsValue->SetInteger("height", bounds.height());
105  result->Set("bounds", boundsValue);
106
107  const ShellWindow::SizeConstraints& size_constraints =
108      window->size_constraints();
109  gfx::Size min_size = size_constraints.GetMinimumSize();
110  gfx::Size max_size = size_constraints.GetMaximumSize();
111  if (min_size.width() != kUnboundedSize)
112    result->SetInteger("minWidth", min_size.width());
113  if (min_size.height() != kUnboundedSize)
114    result->SetInteger("minHeight", min_size.height());
115  if (max_size.width() != kUnboundedSize)
116    result->SetInteger("maxWidth", max_size.width());
117  if (max_size.height() != kUnboundedSize)
118    result->SetInteger("maxHeight", max_size.height());
119}
120
121}  // namespace
122
123void AppWindowCreateFunction::SendDelayedResponse() {
124  SendResponse(true);
125}
126
127bool AppWindowCreateFunction::RunImpl() {
128  // Don't create app window if the system is shutting down.
129  if (g_browser_process->IsShuttingDown())
130    return false;
131
132  scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
133  EXTENSION_FUNCTION_VALIDATE(params.get());
134
135  GURL url = GetExtension()->GetResourceURL(params->url);
136  // Allow absolute URLs for component apps, otherwise prepend the extension
137  // path.
138  if (GetExtension()->location() == extensions::Manifest::COMPONENT) {
139    GURL absolute = GURL(params->url);
140    if (absolute.has_scheme())
141      url = absolute;
142  }
143
144  bool inject_html_titlebar = false;
145
146  // TODO(jeremya): figure out a way to pass the opening WebContents through to
147  // ShellWindow::Create so we can set the opener at create time rather than
148  // with a hack in AppWindowCustomBindings::GetView().
149  ShellWindow::CreateParams create_params;
150  app_window::CreateWindowOptions* options = params->options.get();
151  if (options) {
152    if (options->id.get()) {
153      // TODO(mek): use URL if no id specified?
154      // Limit length of id to 256 characters.
155      if (options->id->length() > 256) {
156        error_ = app_window_constants::kInvalidWindowId;
157        return false;
158      }
159
160      create_params.window_key = *options->id;
161
162      if (options->singleton && *options->singleton == false) {
163        WriteToConsole(
164          content::CONSOLE_MESSAGE_LEVEL_WARNING,
165          "The 'singleton' option in chrome.apps.window.create() is deprecated!"
166          " Change your code to no longer rely on this.");
167      }
168
169      if (!options->singleton || *options->singleton) {
170        ShellWindow* window = apps::ShellWindowRegistry::Get(
171            GetProfile())->GetShellWindowForAppAndKey(extension_id(),
172                                                      create_params.window_key);
173        if (window) {
174          content::RenderViewHost* created_view =
175              window->web_contents()->GetRenderViewHost();
176          int view_id = MSG_ROUTING_NONE;
177          if (render_view_host_->GetProcess()->GetID() ==
178              created_view->GetProcess()->GetID()) {
179            view_id = created_view->GetRoutingID();
180          }
181
182          if (options->focused.get() && !*options->focused.get())
183            window->Show(ShellWindow::SHOW_INACTIVE);
184          else
185            window->Show(ShellWindow::SHOW_ACTIVE);
186
187          base::DictionaryValue* result = new base::DictionaryValue;
188          result->Set("viewId", new base::FundamentalValue(view_id));
189          SetCreateResultFromShellWindow(window, result);
190          result->SetBoolean("existingWindow", true);
191          result->SetBoolean("injectTitlebar", false);
192          SetResult(result);
193          SendResponse(true);
194          return true;
195        }
196      }
197    }
198
199    // TODO(jeremya): remove these, since they do the same thing as
200    // left/top/width/height.
201    if (options->default_width.get())
202      create_params.bounds.set_width(*options->default_width.get());
203    if (options->default_height.get())
204      create_params.bounds.set_height(*options->default_height.get());
205    if (options->default_left.get())
206      create_params.bounds.set_x(*options->default_left.get());
207    if (options->default_top.get())
208      create_params.bounds.set_y(*options->default_top.get());
209
210    if (options->width.get())
211      create_params.bounds.set_width(*options->width.get());
212    if (options->height.get())
213      create_params.bounds.set_height(*options->height.get());
214    if (options->left.get())
215      create_params.bounds.set_x(*options->left.get());
216    if (options->top.get())
217      create_params.bounds.set_y(*options->top.get());
218
219    if (options->bounds.get()) {
220      app_window::Bounds* bounds = options->bounds.get();
221      if (bounds->width.get())
222        create_params.bounds.set_width(*bounds->width.get());
223      if (bounds->height.get())
224        create_params.bounds.set_height(*bounds->height.get());
225      if (bounds->left.get())
226        create_params.bounds.set_x(*bounds->left.get());
227      if (bounds->top.get())
228        create_params.bounds.set_y(*bounds->top.get());
229    }
230
231    if (GetCurrentChannel() <= chrome::VersionInfo::CHANNEL_DEV ||
232        GetExtension()->location() == extensions::Manifest::COMPONENT) {
233      if (options->type == extensions::api::app_window::WINDOW_TYPE_PANEL) {
234          create_params.window_type = ShellWindow::WINDOW_TYPE_PANEL;
235      }
236    }
237
238    if (options->frame.get()) {
239      if (*options->frame == kHtmlFrameOption &&
240          (GetExtension()->HasAPIPermission(APIPermission::kExperimental) ||
241           CommandLine::ForCurrentProcess()->HasSwitch(
242               switches::kEnableExperimentalExtensionApis))) {
243        create_params.frame = ShellWindow::FRAME_NONE;
244        inject_html_titlebar = true;
245      } else if (*options->frame == kNoneFrameOption) {
246        create_params.frame = ShellWindow::FRAME_NONE;
247      } else {
248        create_params.frame = ShellWindow::FRAME_CHROME;
249      }
250    }
251
252    if (options->transparent_background.get() &&
253        (GetExtension()->HasAPIPermission(APIPermission::kExperimental) ||
254         CommandLine::ForCurrentProcess()->HasSwitch(
255             switches::kEnableExperimentalExtensionApis))) {
256      create_params.transparent_background = *options->transparent_background;
257    }
258
259    gfx::Size& minimum_size = create_params.minimum_size;
260    if (options->min_width.get())
261      minimum_size.set_width(*options->min_width);
262    if (options->min_height.get())
263      minimum_size.set_height(*options->min_height);
264    gfx::Size& maximum_size = create_params.maximum_size;
265    if (options->max_width.get())
266      maximum_size.set_width(*options->max_width);
267    if (options->max_height.get())
268      maximum_size.set_height(*options->max_height);
269
270    if (options->hidden.get())
271      create_params.hidden = *options->hidden.get();
272
273    if (options->resizable.get())
274      create_params.resizable = *options->resizable.get();
275
276    if (options->always_on_top.get() &&
277        GetExtension()->HasAPIPermission(APIPermission::kAlwaysOnTopWindows))
278      create_params.always_on_top = *options->always_on_top.get();
279
280    if (options->focused.get())
281      create_params.focused = *options->focused.get();
282
283    if (options->type != extensions::api::app_window::WINDOW_TYPE_PANEL) {
284      switch (options->state) {
285        case extensions::api::app_window::STATE_NONE:
286        case extensions::api::app_window::STATE_NORMAL:
287          break;
288        case extensions::api::app_window::STATE_FULLSCREEN:
289          create_params.state = ui::SHOW_STATE_FULLSCREEN;
290          break;
291        case extensions::api::app_window::STATE_MAXIMIZED:
292          create_params.state = ui::SHOW_STATE_MAXIMIZED;
293          break;
294        case extensions::api::app_window::STATE_MINIMIZED:
295          create_params.state = ui::SHOW_STATE_MINIMIZED;
296          break;
297      }
298    }
299  }
300
301  create_params.creator_process_id =
302      render_view_host_->GetProcess()->GetID();
303
304  ShellWindow* shell_window = new ShellWindow(
305      GetProfile(), new ChromeShellWindowDelegate(), GetExtension());
306  shell_window->Init(url,
307                     new apps::AppWindowContents(shell_window),
308                     create_params);
309
310  if (chrome::IsRunningInForcedAppMode())
311    shell_window->ForcedFullscreen();
312
313  content::RenderViewHost* created_view =
314      shell_window->web_contents()->GetRenderViewHost();
315  int view_id = MSG_ROUTING_NONE;
316  if (create_params.creator_process_id == created_view->GetProcess()->GetID())
317    view_id = created_view->GetRoutingID();
318
319  base::DictionaryValue* result = new base::DictionaryValue;
320  result->Set("viewId", new base::FundamentalValue(view_id));
321  result->Set("injectTitlebar",
322      new base::FundamentalValue(inject_html_titlebar));
323  result->Set("id", new base::StringValue(shell_window->window_key()));
324  SetCreateResultFromShellWindow(shell_window, result);
325  SetResult(result);
326
327  if (apps::ShellWindowRegistry::Get(GetProfile())
328          ->HadDevToolsAttached(created_view)) {
329    new DevToolsRestorer(this, created_view);
330    return true;
331  }
332
333  SendResponse(true);
334  return true;
335}
336
337}  // namespace extensions
338