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