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.
5 #include "chrome/browser/extensions/api/app_window/app_window_api.h"
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"
33 #include "ash/shell.h"
34 #include "ui/aura/root_window.h"
35 #include "ui/aura/window.h"
38 using apps::ShellWindow;
40 namespace app_window = extensions::api::app_window;
41 namespace Create = app_window::Create;
43 namespace extensions {
45 namespace app_window_constants {
46 const char kInvalidWindowId[] =
47 "The window id can not be more than 256 characters long.";
50 const char kNoneFrameOption[] = "none";
51 const char kHtmlFrameOption[] = "experimental-html";
55 const int kUnboundedSize = apps::ShellWindow::SizeConstraints::kUnboundedSize;
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.
60 class DevToolsRestorer : public content::NotificationObserver {
62 DevToolsRestorer(AppWindowCreateFunction* delayed_create_function,
63 content::RenderViewHost* created_view)
64 : delayed_create_function_(delayed_create_function) {
65 DevToolsWindow* devtools_window =
66 DevToolsWindow::OpenDevToolsWindow(
68 DevToolsToggleAction::ShowConsole());
72 content::NOTIFICATION_LOAD_STOP,
73 content::Source<content::NavigationController>(
74 &devtools_window->web_contents()->GetController()));
78 // content::NotificationObserver:
79 virtual void Observe(int type,
80 const content::NotificationSource& source,
81 const content::NotificationDetails& details) OVERRIDE {
82 DCHECK(type == content::NOTIFICATION_LOAD_STOP);
83 delayed_create_function_->SendDelayedResponse();
88 scoped_refptr<AppWindowCreateFunction> delayed_create_function_;
89 content::NotificationRegistrar registrar_;
92 void SetCreateResultFromShellWindow(ShellWindow* window,
93 base::DictionaryValue* result) {
94 result->SetBoolean("fullscreen", window->GetBaseWindow()->IsFullscreen());
95 result->SetBoolean("minimized", window->GetBaseWindow()->IsMinimized());
96 result->SetBoolean("maximized", window->GetBaseWindow()->IsMaximized());
97 result->SetBoolean("alwaysOnTop", window->IsAlwaysOnTop());
98 base::DictionaryValue* boundsValue = new base::DictionaryValue();
99 gfx::Rect bounds = window->GetClientBounds();
100 boundsValue->SetInteger("left", bounds.x());
101 boundsValue->SetInteger("top", bounds.y());
102 boundsValue->SetInteger("width", bounds.width());
103 boundsValue->SetInteger("height", bounds.height());
104 result->Set("bounds", boundsValue);
106 const ShellWindow::SizeConstraints& size_constraints =
107 window->size_constraints();
108 gfx::Size min_size = size_constraints.GetMinimumSize();
109 gfx::Size max_size = size_constraints.GetMaximumSize();
110 if (min_size.width() != kUnboundedSize)
111 result->SetInteger("minWidth", min_size.width());
112 if (min_size.height() != kUnboundedSize)
113 result->SetInteger("minHeight", min_size.height());
114 if (max_size.width() != kUnboundedSize)
115 result->SetInteger("maxWidth", max_size.width());
116 if (max_size.height() != kUnboundedSize)
117 result->SetInteger("maxHeight", max_size.height());
122 void AppWindowCreateFunction::SendDelayedResponse() {
126 bool AppWindowCreateFunction::RunImpl() {
127 // Don't create app window if the system is shutting down.
128 if (g_browser_process->IsShuttingDown())
131 scoped_ptr<Create::Params> params(Create::Params::Create(*args_));
132 EXTENSION_FUNCTION_VALIDATE(params.get());
134 GURL url = GetExtension()->GetResourceURL(params->url);
135 // Allow absolute URLs for component apps, otherwise prepend the extension
137 if (GetExtension()->location() == extensions::Manifest::COMPONENT) {
138 GURL absolute = GURL(params->url);
139 if (absolute.has_scheme())
143 bool inject_html_titlebar = false;
145 // TODO(jeremya): figure out a way to pass the opening WebContents through to
146 // ShellWindow::Create so we can set the opener at create time rather than
147 // with a hack in AppWindowCustomBindings::GetView().
148 ShellWindow::CreateParams create_params;
149 app_window::CreateWindowOptions* options = params->options.get();
151 if (options->id.get()) {
152 // TODO(mek): use URL if no id specified?
153 // Limit length of id to 256 characters.
154 if (options->id->length() > 256) {
155 error_ = app_window_constants::kInvalidWindowId;
159 create_params.window_key = *options->id;
161 if (options->singleton && *options->singleton == false) {
163 content::CONSOLE_MESSAGE_LEVEL_WARNING,
164 "The 'singleton' option in chrome.apps.window.create() is deprecated!"
165 " Change your code to no longer rely on this.");
168 if (!options->singleton || *options->singleton) {
169 ShellWindow* window = apps::ShellWindowRegistry::Get(
170 GetProfile())->GetShellWindowForAppAndKey(extension_id(),
171 create_params.window_key);
173 content::RenderViewHost* created_view =
174 window->web_contents()->GetRenderViewHost();
175 int view_id = MSG_ROUTING_NONE;
176 if (render_view_host_->GetProcess()->GetID() ==
177 created_view->GetProcess()->GetID()) {
178 view_id = created_view->GetRoutingID();
181 if (options->focused.get() && !*options->focused.get())
182 window->Show(ShellWindow::SHOW_INACTIVE);
184 window->Show(ShellWindow::SHOW_ACTIVE);
186 base::DictionaryValue* result = new base::DictionaryValue;
187 result->Set("viewId", new base::FundamentalValue(view_id));
188 SetCreateResultFromShellWindow(window, result);
189 result->SetBoolean("existingWindow", true);
190 result->SetBoolean("injectTitlebar", false);
198 // TODO(jeremya): remove these, since they do the same thing as
199 // left/top/width/height.
200 if (options->default_width.get())
201 create_params.bounds.set_width(*options->default_width.get());
202 if (options->default_height.get())
203 create_params.bounds.set_height(*options->default_height.get());
204 if (options->default_left.get())
205 create_params.bounds.set_x(*options->default_left.get());
206 if (options->default_top.get())
207 create_params.bounds.set_y(*options->default_top.get());
209 if (options->width.get())
210 create_params.bounds.set_width(*options->width.get());
211 if (options->height.get())
212 create_params.bounds.set_height(*options->height.get());
213 if (options->left.get())
214 create_params.bounds.set_x(*options->left.get());
215 if (options->top.get())
216 create_params.bounds.set_y(*options->top.get());
218 if (options->bounds.get()) {
219 app_window::Bounds* bounds = options->bounds.get();
220 if (bounds->width.get())
221 create_params.bounds.set_width(*bounds->width.get());
222 if (bounds->height.get())
223 create_params.bounds.set_height(*bounds->height.get());
224 if (bounds->left.get())
225 create_params.bounds.set_x(*bounds->left.get());
226 if (bounds->top.get())
227 create_params.bounds.set_y(*bounds->top.get());
230 if (GetCurrentChannel() <= chrome::VersionInfo::CHANNEL_DEV ||
231 GetExtension()->location() == extensions::Manifest::COMPONENT) {
232 if (options->type == extensions::api::app_window::WINDOW_TYPE_PANEL) {
233 create_params.window_type = ShellWindow::WINDOW_TYPE_PANEL;
237 if (options->frame.get()) {
238 if (*options->frame == kHtmlFrameOption &&
239 (GetExtension()->HasAPIPermission(APIPermission::kExperimental) ||
240 CommandLine::ForCurrentProcess()->HasSwitch(
241 switches::kEnableExperimentalExtensionApis))) {
242 create_params.frame = ShellWindow::FRAME_NONE;
243 inject_html_titlebar = true;
244 } else if (*options->frame == kNoneFrameOption) {
245 create_params.frame = ShellWindow::FRAME_NONE;
247 create_params.frame = ShellWindow::FRAME_CHROME;
251 if (options->transparent_background.get() &&
252 (GetExtension()->HasAPIPermission(APIPermission::kExperimental) ||
253 CommandLine::ForCurrentProcess()->HasSwitch(
254 switches::kEnableExperimentalExtensionApis))) {
255 create_params.transparent_background = *options->transparent_background;
258 gfx::Size& minimum_size = create_params.minimum_size;
259 if (options->min_width.get())
260 minimum_size.set_width(*options->min_width);
261 if (options->min_height.get())
262 minimum_size.set_height(*options->min_height);
263 gfx::Size& maximum_size = create_params.maximum_size;
264 if (options->max_width.get())
265 maximum_size.set_width(*options->max_width);
266 if (options->max_height.get())
267 maximum_size.set_height(*options->max_height);
269 if (options->hidden.get())
270 create_params.hidden = *options->hidden.get();
272 if (options->resizable.get())
273 create_params.resizable = *options->resizable.get();
275 if (options->always_on_top.get() &&
276 GetExtension()->HasAPIPermission(APIPermission::kAlwaysOnTopWindows))
277 create_params.always_on_top = *options->always_on_top.get();
279 if (options->focused.get())
280 create_params.focused = *options->focused.get();
282 if (options->type != extensions::api::app_window::WINDOW_TYPE_PANEL) {
283 switch (options->state) {
284 case extensions::api::app_window::STATE_NONE:
285 case extensions::api::app_window::STATE_NORMAL:
287 case extensions::api::app_window::STATE_FULLSCREEN:
288 create_params.state = ui::SHOW_STATE_FULLSCREEN;
290 case extensions::api::app_window::STATE_MAXIMIZED:
291 create_params.state = ui::SHOW_STATE_MAXIMIZED;
293 case extensions::api::app_window::STATE_MINIMIZED:
294 create_params.state = ui::SHOW_STATE_MINIMIZED;
300 create_params.creator_process_id =
301 render_view_host_->GetProcess()->GetID();
303 ShellWindow* shell_window = new ShellWindow(
304 GetProfile(), new ChromeShellWindowDelegate(), GetExtension());
305 shell_window->Init(url,
306 new apps::AppWindowContents(shell_window),
309 if (chrome::IsRunningInForcedAppMode())
310 shell_window->ForcedFullscreen();
312 content::RenderViewHost* created_view =
313 shell_window->web_contents()->GetRenderViewHost();
314 int view_id = MSG_ROUTING_NONE;
315 if (create_params.creator_process_id == created_view->GetProcess()->GetID())
316 view_id = created_view->GetRoutingID();
318 base::DictionaryValue* result = new base::DictionaryValue;
319 result->Set("viewId", new base::FundamentalValue(view_id));
320 result->Set("injectTitlebar",
321 new base::FundamentalValue(inject_html_titlebar));
322 result->Set("id", new base::StringValue(shell_window->window_key()));
323 SetCreateResultFromShellWindow(shell_window, result);
326 if (apps::ShellWindowRegistry::Get(GetProfile())
327 ->HadDevToolsAttached(created_view)) {
328 new DevToolsRestorer(this, created_view);
336 } // namespace extensions