1 // Copyright 2014 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/app_window/app_window.h"
6 
7 #include <stddef.h>
8 
9 #include <algorithm>
10 #include <string>
11 #include <utility>
12 #include <vector>
13 
14 #include "base/bind.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/task_runner.h"
19 #include "base/threading/thread_task_runner_handle.h"
20 #include "base/values.h"
21 #include "build/build_config.h"
22 #include "build/chromeos_buildflags.h"
23 #include "components/web_modal/web_contents_modal_dialog_manager.h"
24 #include "content/public/browser/browser_context.h"
25 #include "content/public/browser/file_select_listener.h"
26 #include "content/public/browser/invalidate_type.h"
27 #include "content/public/browser/keyboard_event_processing_result.h"
28 #include "content/public/browser/navigation_entry.h"
29 #include "content/public/browser/render_view_host.h"
30 #include "content/public/browser/render_widget_host.h"
31 #include "content/public/browser/web_contents.h"
32 #include "extensions/browser/app_window/app_delegate.h"
33 #include "extensions/browser/app_window/app_web_contents_helper.h"
34 #include "extensions/browser/app_window/app_window_client.h"
35 #include "extensions/browser/app_window/app_window_geometry_cache.h"
36 #include "extensions/browser/app_window/app_window_registry.h"
37 #include "extensions/browser/app_window/native_app_window.h"
38 #include "extensions/browser/app_window/size_constraints.h"
39 #include "extensions/browser/extension_registry.h"
40 #include "extensions/browser/extension_system.h"
41 #include "extensions/browser/extension_web_contents_observer.h"
42 #include "extensions/browser/extensions_browser_client.h"
43 #include "extensions/browser/notification_types.h"
44 #include "extensions/browser/process_manager.h"
45 #include "extensions/browser/suggest_permission_util.h"
46 #include "extensions/browser/view_type_utils.h"
47 #include "extensions/common/draggable_region.h"
48 #include "extensions/common/extension.h"
49 #include "extensions/common/extension_messages.h"
50 #include "extensions/common/manifest_handlers/icons_handler.h"
51 #include "extensions/common/permissions/permissions_data.h"
52 #include "ipc/ipc_message_macros.h"
53 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
54 #include "third_party/skia/include/core/SkBitmap.h"
55 #include "third_party/skia/include/core/SkRegion.h"
56 #include "ui/display/display.h"
57 #include "ui/display/screen.h"
58 #include "ui/events/keycodes/keyboard_codes.h"
59 
60 #if !defined(OS_MAC)
61 #include "components/prefs/pref_service.h"
62 #include "extensions/browser/pref_names.h"
63 #endif
64 
65 using blink::mojom::ConsoleMessageLevel;
66 using content::BrowserContext;
67 using content::WebContents;
68 using web_modal::WebContentsModalDialogHost;
69 using web_modal::WebContentsModalDialogManager;
70 
71 namespace extensions {
72 
73 namespace {
74 
75 const int kDefaultWidth = 512;
76 const int kDefaultHeight = 384;
77 
SetConstraintProperty(const std::string & name,int value,base::DictionaryValue * bounds_properties)78 void SetConstraintProperty(const std::string& name,
79                            int value,
80                            base::DictionaryValue* bounds_properties) {
81   if (value != SizeConstraints::kUnboundedSize)
82     bounds_properties->SetInteger(name, value);
83   else
84     bounds_properties->Set(name, std::make_unique<base::Value>());
85 }
86 
SetBoundsProperties(const gfx::Rect & bounds,const gfx::Size & min_size,const gfx::Size & max_size,const std::string & bounds_name,base::DictionaryValue * window_properties)87 void SetBoundsProperties(const gfx::Rect& bounds,
88                          const gfx::Size& min_size,
89                          const gfx::Size& max_size,
90                          const std::string& bounds_name,
91                          base::DictionaryValue* window_properties) {
92   std::unique_ptr<base::DictionaryValue> bounds_properties(
93       new base::DictionaryValue());
94 
95   bounds_properties->SetInteger("left", bounds.x());
96   bounds_properties->SetInteger("top", bounds.y());
97   bounds_properties->SetInteger("width", bounds.width());
98   bounds_properties->SetInteger("height", bounds.height());
99 
100   SetConstraintProperty("minWidth", min_size.width(), bounds_properties.get());
101   SetConstraintProperty(
102       "minHeight", min_size.height(), bounds_properties.get());
103   SetConstraintProperty("maxWidth", max_size.width(), bounds_properties.get());
104   SetConstraintProperty(
105       "maxHeight", max_size.height(), bounds_properties.get());
106 
107   window_properties->Set(bounds_name, std::move(bounds_properties));
108 }
109 
110 // Combines the constraints of the content and window, and returns constraints
111 // for the window.
GetCombinedWindowConstraints(const gfx::Size & window_constraints,const gfx::Size & content_constraints,const gfx::Insets & frame_insets)112 gfx::Size GetCombinedWindowConstraints(const gfx::Size& window_constraints,
113                                        const gfx::Size& content_constraints,
114                                        const gfx::Insets& frame_insets) {
115   gfx::Size combined_constraints(window_constraints);
116   if (content_constraints.width() > 0) {
117     combined_constraints.set_width(
118         content_constraints.width() + frame_insets.width());
119   }
120   if (content_constraints.height() > 0) {
121     combined_constraints.set_height(
122         content_constraints.height() + frame_insets.height());
123   }
124   return combined_constraints;
125 }
126 
127 // Combines the constraints of the content and window, and returns constraints
128 // for the content.
GetCombinedContentConstraints(const gfx::Size & window_constraints,const gfx::Size & content_constraints,const gfx::Insets & frame_insets)129 gfx::Size GetCombinedContentConstraints(const gfx::Size& window_constraints,
130                                         const gfx::Size& content_constraints,
131                                         const gfx::Insets& frame_insets) {
132   gfx::Size combined_constraints(content_constraints);
133   if (window_constraints.width() > 0) {
134     combined_constraints.set_width(
135         std::max(0, window_constraints.width() - frame_insets.width()));
136   }
137   if (window_constraints.height() > 0) {
138     combined_constraints.set_height(
139         std::max(0, window_constraints.height() - frame_insets.height()));
140   }
141   return combined_constraints;
142 }
143 
144 }  // namespace
145 
146 // AppWindow::BoundsSpecification
147 
148 const int AppWindow::BoundsSpecification::kUnspecifiedPosition = INT_MIN;
149 
BoundsSpecification()150 AppWindow::BoundsSpecification::BoundsSpecification()
151     : bounds(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0) {}
152 
~BoundsSpecification()153 AppWindow::BoundsSpecification::~BoundsSpecification() {}
154 
ResetBounds()155 void AppWindow::BoundsSpecification::ResetBounds() {
156   bounds.SetRect(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0);
157 }
158 
159 // AppWindow::CreateParams
160 
CreateParams()161 AppWindow::CreateParams::CreateParams()
162     : window_type(AppWindow::WINDOW_TYPE_DEFAULT),
163       frame(AppWindow::FRAME_CHROME),
164       has_frame_color(false),
165       active_frame_color(SK_ColorBLACK),
166       inactive_frame_color(SK_ColorBLACK),
167       alpha_enabled(false),
168       is_ime_window(false),
169       creator_process_id(0),
170       state(ui::SHOW_STATE_DEFAULT),
171       hidden(false),
172       resizable(true),
173       focused(true),
174       always_on_top(false),
175       visible_on_all_workspaces(false),
176       show_on_lock_screen(false),
177       show_in_shelf(false) {}
178 
179 AppWindow::CreateParams::CreateParams(const CreateParams& other) = default;
180 
~CreateParams()181 AppWindow::CreateParams::~CreateParams() {}
182 
GetInitialWindowBounds(const gfx::Insets & frame_insets) const183 gfx::Rect AppWindow::CreateParams::GetInitialWindowBounds(
184     const gfx::Insets& frame_insets) const {
185   // Combine into a single window bounds.
186   gfx::Rect combined_bounds(window_spec.bounds);
187   if (content_spec.bounds.x() != BoundsSpecification::kUnspecifiedPosition)
188     combined_bounds.set_x(content_spec.bounds.x() - frame_insets.left());
189   if (content_spec.bounds.y() != BoundsSpecification::kUnspecifiedPosition)
190     combined_bounds.set_y(content_spec.bounds.y() - frame_insets.top());
191   if (content_spec.bounds.width() > 0) {
192     combined_bounds.set_width(
193         content_spec.bounds.width() + frame_insets.width());
194   }
195   if (content_spec.bounds.height() > 0) {
196     combined_bounds.set_height(
197         content_spec.bounds.height() + frame_insets.height());
198   }
199 
200   // Constrain the bounds.
201   SizeConstraints constraints(
202       GetCombinedWindowConstraints(
203           window_spec.minimum_size, content_spec.minimum_size, frame_insets),
204       GetCombinedWindowConstraints(
205           window_spec.maximum_size, content_spec.maximum_size, frame_insets));
206   combined_bounds.set_size(constraints.ClampSize(combined_bounds.size()));
207 
208   return combined_bounds;
209 }
210 
GetContentMinimumSize(const gfx::Insets & frame_insets) const211 gfx::Size AppWindow::CreateParams::GetContentMinimumSize(
212     const gfx::Insets& frame_insets) const {
213   return GetCombinedContentConstraints(window_spec.minimum_size,
214                                        content_spec.minimum_size,
215                                        frame_insets);
216 }
217 
GetContentMaximumSize(const gfx::Insets & frame_insets) const218 gfx::Size AppWindow::CreateParams::GetContentMaximumSize(
219     const gfx::Insets& frame_insets) const {
220   return GetCombinedContentConstraints(window_spec.maximum_size,
221                                        content_spec.maximum_size,
222                                        frame_insets);
223 }
224 
GetWindowMinimumSize(const gfx::Insets & frame_insets) const225 gfx::Size AppWindow::CreateParams::GetWindowMinimumSize(
226     const gfx::Insets& frame_insets) const {
227   return GetCombinedWindowConstraints(window_spec.minimum_size,
228                                       content_spec.minimum_size,
229                                       frame_insets);
230 }
231 
GetWindowMaximumSize(const gfx::Insets & frame_insets) const232 gfx::Size AppWindow::CreateParams::GetWindowMaximumSize(
233     const gfx::Insets& frame_insets) const {
234   return GetCombinedWindowConstraints(window_spec.maximum_size,
235                                       content_spec.maximum_size,
236                                       frame_insets);
237 }
238 
239 // AppWindow
240 
AppWindow(BrowserContext * context,AppDelegate * app_delegate,const Extension * extension)241 AppWindow::AppWindow(BrowserContext* context,
242                      AppDelegate* app_delegate,
243                      const Extension* extension)
244     : browser_context_(context),
245       extension_id_(extension->id()),
246       session_id_(SessionID::NewUnique()),
247       app_delegate_(app_delegate) {
248   ExtensionsBrowserClient* client = ExtensionsBrowserClient::Get();
249   CHECK(!client->IsGuestSession(context) || context->IsOffTheRecord())
250       << "Only off the record window may be opened in the guest mode.";
251 }
252 
Init(const GURL & url,AppWindowContents * app_window_contents,content::RenderFrameHost * creator_frame,const CreateParams & params)253 void AppWindow::Init(const GURL& url,
254                      AppWindowContents* app_window_contents,
255                      content::RenderFrameHost* creator_frame,
256                      const CreateParams& params) {
257   // Initialize the render interface and web contents
258   app_window_contents_.reset(app_window_contents);
259   app_window_contents_->Initialize(browser_context(), creator_frame, url);
260 
261   initial_url_ = url;
262 
263   content::WebContentsObserver::Observe(web_contents());
264   SetViewType(web_contents(), VIEW_TYPE_APP_WINDOW);
265   app_delegate_->InitWebContents(web_contents());
266 
267   ExtensionWebContentsObserver::GetForWebContents(web_contents())->
268       dispatcher()->set_delegate(this);
269 
270   WebContentsModalDialogManager::CreateForWebContents(web_contents());
271 
272   web_contents()->SetDelegate(this);
273   WebContentsModalDialogManager::FromWebContents(web_contents())
274       ->SetDelegate(this);
275 
276   // Initialize the window
277   CreateParams new_params = LoadDefaults(params);
278   window_type_ = new_params.window_type;
279   window_key_ = new_params.window_key;
280 
281   // Windows cannot be always-on-top in fullscreen mode for security reasons.
282   cached_always_on_top_ = new_params.always_on_top;
283   if (new_params.state == ui::SHOW_STATE_FULLSCREEN &&
284       !ExtensionsBrowserClient::Get()->IsScreensaverInDemoMode(
285           extension_id())) {
286     new_params.always_on_top = false;
287   }
288 
289   requested_alpha_enabled_ = new_params.alpha_enabled;
290   is_ime_window_ = params.is_ime_window;
291   show_on_lock_screen_ = params.show_on_lock_screen;
292   show_in_shelf_ = params.show_in_shelf;
293 
294   AppWindowClient* app_window_client = AppWindowClient::Get();
295   native_app_window_.reset(
296       app_window_client->CreateNativeAppWindow(this, &new_params));
297 
298   helper_.reset(new AppWebContentsHelper(
299       browser_context_, extension_id_, web_contents(), app_delegate_.get()));
300 
301   native_app_window_->UpdateWindowIcon();
302 
303   if (params.window_icon_url.is_valid())
304     SetAppIconUrl(params.window_icon_url);
305 
306   AppWindowRegistry::Get(browser_context_)->AddAppWindow(this);
307 
308   if (new_params.hidden) {
309     // Although the window starts hidden by default, calling Hide() here
310     // notifies observers of the window being hidden.
311     Hide();
312   } else {
313     Show(SHOW_INACTIVE);
314 
315     // These states may cause the window to show, so they are ignored if the
316     // window is initially hidden.
317     if (new_params.state == ui::SHOW_STATE_FULLSCREEN)
318       Fullscreen();
319     else if (new_params.state == ui::SHOW_STATE_MAXIMIZED)
320       Maximize();
321     else if (new_params.state == ui::SHOW_STATE_MINIMIZED)
322       Minimize();
323 
324     Show(new_params.focused ? SHOW_ACTIVE : SHOW_INACTIVE);
325   }
326 
327   OnNativeWindowChanged();
328 
329   ExtensionRegistry::Get(browser_context_)->AddObserver(this);
330 
331   // Close when the browser process is exiting.
332   app_delegate_->SetTerminatingCallback(base::BindOnce(
333       &NativeAppWindow::Close, base::Unretained(native_app_window_.get())));
334 
335   app_window_contents_->LoadContents(new_params.creator_process_id);
336 }
337 
~AppWindow()338 AppWindow::~AppWindow() {
339   ExtensionRegistry::Get(browser_context_)->RemoveObserver(this);
340 }
341 
RequestMediaAccessPermission(content::WebContents * web_contents,const content::MediaStreamRequest & request,content::MediaResponseCallback callback)342 void AppWindow::RequestMediaAccessPermission(
343     content::WebContents* web_contents,
344     const content::MediaStreamRequest& request,
345     content::MediaResponseCallback callback) {
346   DCHECK_EQ(AppWindow::web_contents(), web_contents);
347   helper_->RequestMediaAccessPermission(request, std::move(callback));
348 }
349 
CheckMediaAccessPermission(content::RenderFrameHost * render_frame_host,const GURL & security_origin,blink::mojom::MediaStreamType type)350 bool AppWindow::CheckMediaAccessPermission(
351     content::RenderFrameHost* render_frame_host,
352     const GURL& security_origin,
353     blink::mojom::MediaStreamType type) {
354   DCHECK_EQ(web_contents(),
355             content::WebContents::FromRenderFrameHost(render_frame_host)
356                 ->GetOutermostWebContents());
357   return helper_->CheckMediaAccessPermission(render_frame_host, security_origin,
358                                              type);
359 }
360 
OpenURLFromTab(WebContents * source,const content::OpenURLParams & params)361 WebContents* AppWindow::OpenURLFromTab(WebContents* source,
362                                        const content::OpenURLParams& params) {
363   DCHECK_EQ(web_contents(), source);
364   return helper_->OpenURLFromTab(params);
365 }
366 
AddNewContents(WebContents * source,std::unique_ptr<WebContents> new_contents,const GURL & target_url,WindowOpenDisposition disposition,const gfx::Rect & initial_rect,bool user_gesture,bool * was_blocked)367 void AppWindow::AddNewContents(WebContents* source,
368                                std::unique_ptr<WebContents> new_contents,
369                                const GURL& target_url,
370                                WindowOpenDisposition disposition,
371                                const gfx::Rect& initial_rect,
372                                bool user_gesture,
373                                bool* was_blocked) {
374   DCHECK(new_contents->GetBrowserContext() == browser_context_);
375   app_delegate_->AddNewContents(browser_context_, std::move(new_contents),
376                                 target_url, disposition, initial_rect,
377                                 user_gesture);
378 }
379 
PreHandleKeyboardEvent(content::WebContents * source,const content::NativeWebKeyboardEvent & event)380 content::KeyboardEventProcessingResult AppWindow::PreHandleKeyboardEvent(
381     content::WebContents* source,
382     const content::NativeWebKeyboardEvent& event) {
383   const Extension* extension = GetExtension();
384   if (!extension)
385     return content::KeyboardEventProcessingResult::NOT_HANDLED;
386 
387   // Here, we can handle a key event before the content gets it. When we are
388   // fullscreen and it is not forced, we want to allow the user to leave
389   // when ESC is pressed.
390   // However, if the application has the "overrideEscFullscreen" permission, we
391   // should let it override that behavior.
392   // ::HandleKeyboardEvent() will only be called if the KeyEvent's default
393   // action is not prevented.
394   // Thus, we should handle the KeyEvent here only if the permission is not set.
395   if (event.windows_key_code == ui::VKEY_ESCAPE && IsFullscreen() &&
396       !IsForcedFullscreen() &&
397       !extension->permissions_data()->HasAPIPermission(
398           APIPermission::kOverrideEscFullscreen)) {
399     Restore();
400     return content::KeyboardEventProcessingResult::HANDLED;
401   }
402 
403   return content::KeyboardEventProcessingResult::NOT_HANDLED;
404 }
405 
HandleKeyboardEvent(WebContents * source,const content::NativeWebKeyboardEvent & event)406 bool AppWindow::HandleKeyboardEvent(
407     WebContents* source,
408     const content::NativeWebKeyboardEvent& event) {
409   // If the window is currently fullscreen and not forced, ESC should leave
410   // fullscreen.  If this code is being called for ESC, that means that the
411   // KeyEvent's default behavior was not prevented by the content.
412   if (event.windows_key_code == ui::VKEY_ESCAPE && IsFullscreen() &&
413       !IsForcedFullscreen()) {
414     Restore();
415     return true;
416   }
417 
418   return native_app_window_->HandleKeyboardEvent(event);
419 }
420 
RequestToLockMouse(WebContents * web_contents,bool user_gesture,bool last_unlocked_by_target)421 void AppWindow::RequestToLockMouse(WebContents* web_contents,
422                                    bool user_gesture,
423                                    bool last_unlocked_by_target) {
424   DCHECK_EQ(AppWindow::web_contents(), web_contents);
425   helper_->RequestToLockMouse();
426 }
427 
PreHandleGestureEvent(WebContents * source,const blink::WebGestureEvent & event)428 bool AppWindow::PreHandleGestureEvent(WebContents* source,
429                                       const blink::WebGestureEvent& event) {
430   return AppWebContentsHelper::ShouldSuppressGestureEvent(event);
431 }
432 
TakeFocus(WebContents * source,bool reverse)433 bool AppWindow::TakeFocus(WebContents* source, bool reverse) {
434   return app_delegate_->TakeFocus(source, reverse);
435 }
436 
EnterPictureInPicture(content::WebContents * web_contents,const viz::SurfaceId & surface_id,const gfx::Size & natural_size)437 content::PictureInPictureResult AppWindow::EnterPictureInPicture(
438     content::WebContents* web_contents,
439     const viz::SurfaceId& surface_id,
440     const gfx::Size& natural_size) {
441   return app_delegate_->EnterPictureInPicture(web_contents, surface_id,
442                                               natural_size);
443 }
444 
ExitPictureInPicture()445 void AppWindow::ExitPictureInPicture() {
446   app_delegate_->ExitPictureInPicture();
447 }
448 
ShouldShowStaleContentOnEviction(content::WebContents * source)449 bool AppWindow::ShouldShowStaleContentOnEviction(content::WebContents* source) {
450 #if BUILDFLAG(IS_CHROMEOS_ASH)
451   return true;
452 #else
453   return false;
454 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
455 }
456 
OnMessageReceived(const IPC::Message & message,content::RenderFrameHost * render_frame_host)457 bool AppWindow::OnMessageReceived(const IPC::Message& message,
458                                   content::RenderFrameHost* render_frame_host) {
459   bool handled = true;
460   IPC_BEGIN_MESSAGE_MAP(AppWindow, message)
461     IPC_MESSAGE_HANDLER(ExtensionHostMsg_AppWindowReady, OnAppWindowReady)
462     IPC_MESSAGE_UNHANDLED(handled = false)
463   IPC_END_MESSAGE_MAP()
464   return handled;
465 }
466 
RenderViewCreated(content::RenderViewHost * render_view_host)467 void AppWindow::RenderViewCreated(content::RenderViewHost* render_view_host) {
468   app_delegate_->RenderViewCreated(render_view_host);
469 }
470 
AddOnDidFinishFirstNavigationCallback(DidFinishFirstNavigationCallback callback)471 void AppWindow::AddOnDidFinishFirstNavigationCallback(
472     DidFinishFirstNavigationCallback callback) {
473   on_did_finish_first_navigation_callbacks_.push_back(std::move(callback));
474 }
475 
OnDidFinishFirstNavigation()476 void AppWindow::OnDidFinishFirstNavigation() {
477   did_finish_first_navigation_ = true;
478   std::vector<DidFinishFirstNavigationCallback> callbacks;
479   std::swap(callbacks, on_did_finish_first_navigation_callbacks_);
480   for (auto&& callback : callbacks)
481     std::move(callback).Run(true /* did_finish */);
482 }
483 
OnNativeClose()484 void AppWindow::OnNativeClose() {
485   AppWindowRegistry::Get(browser_context_)->RemoveAppWindow(this);
486 
487   // Run pending |on_did_finish_first_navigation_callback_| so that
488   // AppWindowCreateFunction can respond with an error properly.
489   std::vector<DidFinishFirstNavigationCallback> callbacks;
490   std::swap(callbacks, on_did_finish_first_navigation_callbacks_);
491   // No "OnClosed" event on window creation error.
492   const bool send_onclosed = callbacks.empty();
493   for (auto&& callback : callbacks)
494     std::move(callback).Run(false /* did_finish */);
495 
496   if (app_window_contents_) {
497     WebContentsModalDialogManager* modal_dialog_manager =
498         WebContentsModalDialogManager::FromWebContents(web_contents());
499     if (modal_dialog_manager)  // May be null in unit tests.
500       modal_dialog_manager->SetDelegate(nullptr);
501     app_window_contents_->NativeWindowClosed(send_onclosed);
502   }
503 
504   delete this;
505 }
506 
OnNativeWindowChanged()507 void AppWindow::OnNativeWindowChanged() {
508   // This may be called during Init before |native_app_window_| is set.
509   if (!native_app_window_)
510     return;
511 
512 #if defined(OS_MAC)
513   // On Mac the user can change the window's fullscreen state. If that has
514   // happened, update AppWindow's internal state.
515   if (native_app_window_->IsFullscreen()) {
516     if (!IsFullscreen())
517       fullscreen_types_ = FULLSCREEN_TYPE_OS;
518   } else {
519     fullscreen_types_ = FULLSCREEN_TYPE_NONE;
520   }
521 
522   RestoreAlwaysOnTop();  // Same as in SetNativeWindowFullscreen.
523 #endif
524 
525   SaveWindowPosition();
526 
527 #if defined(OS_WIN)
528   if (cached_always_on_top_ && !IsFullscreen() &&
529       !native_app_window_->IsMaximized() &&
530       !native_app_window_->IsMinimized()) {
531     UpdateNativeAlwaysOnTop();
532   }
533 #endif
534 
535   if (app_window_contents_)
536     app_window_contents_->NativeWindowChanged(native_app_window_.get());
537 }
538 
OnNativeWindowActivated()539 void AppWindow::OnNativeWindowActivated() {
540   AppWindowRegistry::Get(browser_context_)->AppWindowActivated(this);
541 }
542 
web_contents() const543 content::WebContents* AppWindow::web_contents() const {
544   if (app_window_contents_)
545     return app_window_contents_->GetWebContents();
546   return nullptr;
547 }
548 
GetExtension() const549 const Extension* AppWindow::GetExtension() const {
550   return ExtensionRegistry::Get(browser_context_)
551       ->enabled_extensions()
552       .GetByID(extension_id_);
553 }
554 
GetBaseWindow()555 NativeAppWindow* AppWindow::GetBaseWindow() { return native_app_window_.get(); }
556 
GetNativeWindow()557 gfx::NativeWindow AppWindow::GetNativeWindow() {
558   return GetBaseWindow()->GetNativeWindow();
559 }
560 
GetClientBounds() const561 gfx::Rect AppWindow::GetClientBounds() const {
562   gfx::Rect bounds = native_app_window_->GetBounds();
563   bounds.Inset(native_app_window_->GetFrameInsets());
564   return bounds;
565 }
566 
GetTitle() const567 base::string16 AppWindow::GetTitle() const {
568   const Extension* extension = GetExtension();
569   if (!extension)
570     return base::string16();
571 
572   // WebContents::GetTitle() will return the page's URL if there's no <title>
573   // specified. However, we'd prefer to show the name of the extension in that
574   // case, so we directly inspect the NavigationEntry's title.
575   base::string16 title;
576   content::NavigationEntry* entry = web_contents() ?
577       web_contents()->GetController().GetLastCommittedEntry() : nullptr;
578   if (!entry || entry->GetTitle().empty()) {
579     title = base::UTF8ToUTF16(extension->name());
580   } else {
581     title = web_contents()->GetTitle();
582   }
583   base::RemoveChars(title, base::ASCIIToUTF16("\n"), &title);
584   return title;
585 }
586 
SetAppIconUrl(const GURL & url)587 void AppWindow::SetAppIconUrl(const GURL& url) {
588   app_icon_url_ = url;
589 
590   // Don't start custom app icon loading in the case window is not ready yet.
591   // see crbug.com/788531.
592   if (!window_ready_)
593     return;
594 
595   StartAppIconDownload();
596 }
597 
UpdateShape(std::unique_ptr<ShapeRects> rects)598 void AppWindow::UpdateShape(std::unique_ptr<ShapeRects> rects) {
599   native_app_window_->UpdateShape(std::move(rects));
600 }
601 
UpdateDraggableRegions(const std::vector<DraggableRegion> & regions)602 void AppWindow::UpdateDraggableRegions(
603     const std::vector<DraggableRegion>& regions) {
604   native_app_window_->UpdateDraggableRegions(regions);
605 }
606 
UpdateAppIcon(const gfx::Image & image)607 void AppWindow::UpdateAppIcon(const gfx::Image& image) {
608   if (image.IsEmpty())
609     return;
610   custom_app_icon_ = image;
611   native_app_window_->UpdateWindowIcon();
612 }
613 
SetFullscreen(FullscreenType type,bool enable)614 void AppWindow::SetFullscreen(FullscreenType type, bool enable) {
615   DCHECK_NE(FULLSCREEN_TYPE_NONE, type);
616 
617   if (enable) {
618 #if !defined(OS_MAC)
619     // Do not enter fullscreen mode if disallowed by pref.
620     // TODO(bartfab): Add a test once it becomes possible to simulate a user
621     // gesture. http://crbug.com/174178
622     if (type != FULLSCREEN_TYPE_FORCED) {
623       PrefService* prefs =
624           ExtensionsBrowserClient::Get()->GetPrefServiceForContext(
625               browser_context());
626       if (!prefs->GetBoolean(pref_names::kAppFullscreenAllowed))
627         return;
628     }
629 #endif
630     fullscreen_types_ |= type;
631   } else {
632     fullscreen_types_ &= ~type;
633   }
634   SetNativeWindowFullscreen();
635 }
636 
IsFullscreen() const637 bool AppWindow::IsFullscreen() const {
638   return fullscreen_types_ != FULLSCREEN_TYPE_NONE;
639 }
640 
IsForcedFullscreen() const641 bool AppWindow::IsForcedFullscreen() const {
642   return (fullscreen_types_ & FULLSCREEN_TYPE_FORCED) != 0;
643 }
644 
IsHtmlApiFullscreen() const645 bool AppWindow::IsHtmlApiFullscreen() const {
646   return (fullscreen_types_ & FULLSCREEN_TYPE_HTML_API) != 0;
647 }
648 
IsOsFullscreen() const649 bool AppWindow::IsOsFullscreen() const {
650   return (fullscreen_types_ & FULLSCREEN_TYPE_OS) != 0;
651 }
652 
Fullscreen()653 void AppWindow::Fullscreen() {
654   SetFullscreen(FULLSCREEN_TYPE_WINDOW_API, true);
655 }
656 
Maximize()657 void AppWindow::Maximize() { GetBaseWindow()->Maximize(); }
658 
Minimize()659 void AppWindow::Minimize() { GetBaseWindow()->Minimize(); }
660 
Restore()661 void AppWindow::Restore() {
662   if (IsFullscreen()) {
663     fullscreen_types_ = FULLSCREEN_TYPE_NONE;
664     SetNativeWindowFullscreen();
665   } else {
666     GetBaseWindow()->Restore();
667   }
668 }
669 
OSFullscreen()670 void AppWindow::OSFullscreen() {
671   SetFullscreen(FULLSCREEN_TYPE_OS, true);
672 }
673 
ForcedFullscreen()674 void AppWindow::ForcedFullscreen() {
675   SetFullscreen(FULLSCREEN_TYPE_FORCED, true);
676 }
677 
SetContentSizeConstraints(const gfx::Size & min_size,const gfx::Size & max_size)678 void AppWindow::SetContentSizeConstraints(const gfx::Size& min_size,
679                                           const gfx::Size& max_size) {
680   SizeConstraints constraints(min_size, max_size);
681   native_app_window_->SetContentSizeConstraints(constraints.GetMinimumSize(),
682                                                 constraints.GetMaximumSize());
683 
684   gfx::Rect bounds = GetClientBounds();
685   gfx::Size constrained_size = constraints.ClampSize(bounds.size());
686   if (bounds.size() != constrained_size) {
687     bounds.set_size(constrained_size);
688     bounds.Inset(-native_app_window_->GetFrameInsets());
689     native_app_window_->SetBounds(bounds);
690   }
691   OnNativeWindowChanged();
692 }
693 
Show(ShowType show_type)694 void AppWindow::Show(ShowType show_type) {
695   app_delegate_->OnShow();
696   bool was_hidden = is_hidden_ || !has_been_shown_;
697   is_hidden_ = false;
698 
699   switch (show_type) {
700     case SHOW_ACTIVE:
701       GetBaseWindow()->Show();
702       break;
703     case SHOW_INACTIVE:
704       GetBaseWindow()->ShowInactive();
705       break;
706   }
707   AppWindowRegistry::Get(browser_context_)->AppWindowShown(this, was_hidden);
708   has_been_shown_ = true;
709 }
710 
Hide()711 void AppWindow::Hide() {
712   is_hidden_ = true;
713   GetBaseWindow()->Hide();
714   AppWindowRegistry::Get(browser_context_)->AppWindowHidden(this);
715   app_delegate_->OnHide();
716 }
717 
SetAlwaysOnTop(bool always_on_top)718 void AppWindow::SetAlwaysOnTop(bool always_on_top) {
719   if (cached_always_on_top_ == always_on_top)
720     return;
721 
722   cached_always_on_top_ = always_on_top;
723 
724   // As a security measure, do not allow fullscreen windows or windows that
725   // overlap the taskbar to be on top. The property will be applied when the
726   // window exits fullscreen and moves away from the taskbar.
727   if ((!IsFullscreen() ||
728        ExtensionsBrowserClient::Get()->IsScreensaverInDemoMode(
729            extension_id())) &&
730       !IntersectsWithTaskbar()) {
731     native_app_window_->SetZOrderLevel(always_on_top
732                                            ? ui::ZOrderLevel::kFloatingWindow
733                                            : ui::ZOrderLevel::kNormal);
734   }
735 
736   OnNativeWindowChanged();
737 }
738 
IsAlwaysOnTop() const739 bool AppWindow::IsAlwaysOnTop() const { return cached_always_on_top_; }
740 
RestoreAlwaysOnTop()741 void AppWindow::RestoreAlwaysOnTop() {
742   if (cached_always_on_top_)
743     UpdateNativeAlwaysOnTop();
744 }
745 
GetSerializedState(base::DictionaryValue * properties) const746 void AppWindow::GetSerializedState(base::DictionaryValue* properties) const {
747   DCHECK(properties);
748 
749   properties->SetBoolean("fullscreen",
750                          native_app_window_->IsFullscreenOrPending());
751   properties->SetBoolean("minimized", native_app_window_->IsMinimized());
752   properties->SetBoolean("maximized", native_app_window_->IsMaximized());
753   properties->SetBoolean("alwaysOnTop", IsAlwaysOnTop());
754   properties->SetBoolean("hasFrameColor", native_app_window_->HasFrameColor());
755   properties->SetBoolean(
756       "alphaEnabled",
757       requested_alpha_enabled_ && native_app_window_->CanHaveAlphaEnabled());
758 
759   // These properties are undocumented and are to enable testing. Alpha is
760   // removed to
761   // make the values easier to check.
762   SkColor transparent_white = ~SK_ColorBLACK;
763   properties->SetInteger(
764       "activeFrameColor",
765       native_app_window_->ActiveFrameColor() & transparent_white);
766   properties->SetInteger(
767       "inactiveFrameColor",
768       native_app_window_->InactiveFrameColor() & transparent_white);
769 
770   gfx::Rect content_bounds = GetClientBounds();
771   gfx::Size content_min_size = native_app_window_->GetContentMinimumSize();
772   gfx::Size content_max_size = native_app_window_->GetContentMaximumSize();
773   SetBoundsProperties(content_bounds,
774                       content_min_size,
775                       content_max_size,
776                       "innerBounds",
777                       properties);
778 
779   gfx::Insets frame_insets = native_app_window_->GetFrameInsets();
780   gfx::Rect frame_bounds = native_app_window_->GetBounds();
781   gfx::Size frame_min_size = SizeConstraints::AddFrameToConstraints(
782       content_min_size, frame_insets);
783   gfx::Size frame_max_size = SizeConstraints::AddFrameToConstraints(
784       content_max_size, frame_insets);
785   SetBoundsProperties(frame_bounds,
786                       frame_min_size,
787                       frame_max_size,
788                       "outerBounds",
789                       properties);
790 }
791 
792 //------------------------------------------------------------------------------
793 // Private methods
StartAppIconDownload()794 void AppWindow::StartAppIconDownload() {
795   DCHECK(app_icon_url_.is_valid());
796 
797   // Avoid using any previous icons that were being downloaded.
798   image_loader_ptr_factory_.InvalidateWeakPtrs();
799   web_contents()->DownloadImage(
800       app_icon_url_,
801       true,  // is a favicon
802       app_delegate_->PreferredIconSize(),
803       0,      // no maximum size
804       false,  // normal cache policy
805       base::BindOnce(&AppWindow::DidDownloadFavicon,
806                      image_loader_ptr_factory_.GetWeakPtr()));
807 }
808 
DidDownloadFavicon(int id,int http_status_code,const GURL & image_url,const std::vector<SkBitmap> & bitmaps,const std::vector<gfx::Size> & original_bitmap_sizes)809 void AppWindow::DidDownloadFavicon(
810     int id,
811     int http_status_code,
812     const GURL& image_url,
813     const std::vector<SkBitmap>& bitmaps,
814     const std::vector<gfx::Size>& original_bitmap_sizes) {
815   if (image_url != app_icon_url_ || bitmaps.empty())
816     return;
817 
818   // Bitmaps are ordered largest to smallest. Choose the smallest bitmap
819   // whose height >= the preferred size.
820   int largest_index = 0;
821   for (size_t i = 1; i < bitmaps.size(); ++i) {
822     if (bitmaps[i].height() < app_delegate_->PreferredIconSize())
823       break;
824     largest_index = i;
825   }
826   const SkBitmap& largest = bitmaps[largest_index];
827   UpdateAppIcon(gfx::Image::CreateFrom1xBitmap(largest));
828 }
829 
SetNativeWindowFullscreen()830 void AppWindow::SetNativeWindowFullscreen() {
831   native_app_window_->SetFullscreen(fullscreen_types_);
832 
833   RestoreAlwaysOnTop();
834 }
835 
IntersectsWithTaskbar() const836 bool AppWindow::IntersectsWithTaskbar() const {
837 #if defined(OS_WIN)
838   display::Screen* screen = display::Screen::GetScreen();
839   gfx::Rect window_bounds = native_app_window_->GetRestoredBounds();
840   std::vector<display::Display> displays = screen->GetAllDisplays();
841 
842   for (std::vector<display::Display>::const_iterator it = displays.begin();
843        it != displays.end(); ++it) {
844     gfx::Rect taskbar_bounds = it->bounds();
845     taskbar_bounds.Subtract(it->work_area());
846     if (taskbar_bounds.IsEmpty())
847       continue;
848 
849     if (window_bounds.Intersects(taskbar_bounds))
850       return true;
851   }
852 #endif
853 
854   return false;
855 }
856 
UpdateNativeAlwaysOnTop()857 void AppWindow::UpdateNativeAlwaysOnTop() {
858   DCHECK(cached_always_on_top_);
859   bool is_on_top =
860       native_app_window_->GetZOrderLevel() == ui::ZOrderLevel::kFloatingWindow;
861   bool fullscreen = IsFullscreen();
862   bool intersects_taskbar = IntersectsWithTaskbar();
863 
864   if (is_on_top && (fullscreen || intersects_taskbar)) {
865     // When entering fullscreen or overlapping the taskbar, ensure windows are
866     // not always-on-top.
867     native_app_window_->SetZOrderLevel(ui::ZOrderLevel::kNormal);
868   } else if (!is_on_top && !fullscreen && !intersects_taskbar) {
869     // When exiting fullscreen and moving away from the taskbar, reinstate
870     // always-on-top.
871     native_app_window_->SetZOrderLevel(ui::ZOrderLevel::kFloatingWindow);
872   }
873 }
874 
ActivateContents(WebContents * contents)875 void AppWindow::ActivateContents(WebContents* contents) {
876   native_app_window_->Activate();
877 }
878 
CloseContents(WebContents * contents)879 void AppWindow::CloseContents(WebContents* contents) {
880   native_app_window_->Close();
881 }
882 
ShouldSuppressDialogs(WebContents * source)883 bool AppWindow::ShouldSuppressDialogs(WebContents* source) {
884   return true;
885 }
886 
OpenColorChooser(WebContents * web_contents,SkColor initial_color,const std::vector<blink::mojom::ColorSuggestionPtr> & suggestions)887 content::ColorChooser* AppWindow::OpenColorChooser(
888     WebContents* web_contents,
889     SkColor initial_color,
890     const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
891   return app_delegate_->ShowColorChooser(web_contents, initial_color);
892 }
893 
RunFileChooser(content::RenderFrameHost * render_frame_host,scoped_refptr<content::FileSelectListener> listener,const blink::mojom::FileChooserParams & params)894 void AppWindow::RunFileChooser(
895     content::RenderFrameHost* render_frame_host,
896     scoped_refptr<content::FileSelectListener> listener,
897     const blink::mojom::FileChooserParams& params) {
898   app_delegate_->RunFileChooser(render_frame_host, std::move(listener), params);
899 }
900 
SetContentsBounds(WebContents * source,const gfx::Rect & bounds)901 void AppWindow::SetContentsBounds(WebContents* source,
902                                   const gfx::Rect& bounds) {
903   native_app_window_->SetBounds(bounds);
904 }
905 
NavigationStateChanged(content::WebContents * source,content::InvalidateTypes changed_flags)906 void AppWindow::NavigationStateChanged(content::WebContents* source,
907                                        content::InvalidateTypes changed_flags) {
908   if (changed_flags & content::INVALIDATE_TYPE_TITLE)
909     native_app_window_->UpdateWindowTitle();
910   else if (changed_flags & content::INVALIDATE_TYPE_TAB)
911     native_app_window_->UpdateWindowIcon();
912 }
913 
EnterFullscreenModeForTab(content::RenderFrameHost * requesting_frame,const blink::mojom::FullscreenOptions & options)914 void AppWindow::EnterFullscreenModeForTab(
915     content::RenderFrameHost* requesting_frame,
916     const blink::mojom::FullscreenOptions& options) {
917   ToggleFullscreenModeForTab(WebContents::FromRenderFrameHost(requesting_frame),
918                              true);
919 }
920 
ExitFullscreenModeForTab(content::WebContents * source)921 void AppWindow::ExitFullscreenModeForTab(content::WebContents* source) {
922   ToggleFullscreenModeForTab(source, false);
923 }
924 
OnAppWindowReady()925 void AppWindow::OnAppWindowReady() {
926   window_ready_ = true;
927 
928   if (app_icon_url_.is_valid())
929     StartAppIconDownload();
930 }
931 
ToggleFullscreenModeForTab(content::WebContents * source,bool enter_fullscreen)932 void AppWindow::ToggleFullscreenModeForTab(content::WebContents* source,
933                                            bool enter_fullscreen) {
934   const Extension* extension = GetExtension();
935   if (!extension)
936     return;
937 
938   if (!IsExtensionWithPermissionOrSuggestInConsole(
939           APIPermission::kFullscreen, extension, source->GetMainFrame())) {
940     return;
941   }
942 
943   SetFullscreen(FULLSCREEN_TYPE_HTML_API, enter_fullscreen);
944 }
945 
IsFullscreenForTabOrPending(const content::WebContents * source)946 bool AppWindow::IsFullscreenForTabOrPending(
947     const content::WebContents* source) {
948   return IsHtmlApiFullscreen();
949 }
950 
GetDisplayMode(const content::WebContents * source)951 blink::mojom::DisplayMode AppWindow::GetDisplayMode(
952     const content::WebContents* source) {
953   return IsFullscreen() ? blink::mojom::DisplayMode::kFullscreen
954                         : blink::mojom::DisplayMode::kStandalone;
955 }
956 
GetExtensionWindowController() const957 WindowController* AppWindow::GetExtensionWindowController() const {
958   return app_window_contents_->GetWindowController();
959 }
960 
GetAssociatedWebContents() const961 content::WebContents* AppWindow::GetAssociatedWebContents() const {
962   return web_contents();
963 }
964 
OnExtensionUnloaded(BrowserContext * browser_context,const Extension * extension,UnloadedExtensionReason reason)965 void AppWindow::OnExtensionUnloaded(BrowserContext* browser_context,
966                                     const Extension* extension,
967                                     UnloadedExtensionReason reason) {
968   if (extension_id_ == extension->id())
969     native_app_window_->Close();
970 }
971 
SetWebContentsBlocked(content::WebContents * web_contents,bool blocked)972 void AppWindow::SetWebContentsBlocked(content::WebContents* web_contents,
973                                       bool blocked) {
974   app_delegate_->SetWebContentsBlocked(web_contents, blocked);
975 }
976 
IsWebContentsVisible(content::WebContents * web_contents)977 bool AppWindow::IsWebContentsVisible(content::WebContents* web_contents) {
978   return app_delegate_->IsWebContentsVisible(web_contents);
979 }
980 
GetWebContentsModalDialogHost()981 WebContentsModalDialogHost* AppWindow::GetWebContentsModalDialogHost() {
982   return native_app_window_.get();
983 }
984 
SaveWindowPosition()985 void AppWindow::SaveWindowPosition() {
986   DCHECK(native_app_window_);
987   if (window_key_.empty())
988     return;
989 
990   AppWindowGeometryCache* cache =
991       AppWindowGeometryCache::Get(browser_context());
992 
993   gfx::Rect bounds = native_app_window_->GetRestoredBounds();
994   gfx::Rect screen_bounds =
995       display::Screen::GetScreen()->GetDisplayMatching(bounds).work_area();
996   ui::WindowShowState window_state = native_app_window_->GetRestoredState();
997   cache->SaveGeometry(
998       extension_id(), window_key_, bounds, screen_bounds, window_state);
999 }
1000 
AdjustBoundsToBeVisibleOnScreen(const gfx::Rect & cached_bounds,const gfx::Rect & cached_screen_bounds,const gfx::Rect & current_screen_bounds,const gfx::Size & minimum_size,gfx::Rect * bounds) const1001 void AppWindow::AdjustBoundsToBeVisibleOnScreen(
1002     const gfx::Rect& cached_bounds,
1003     const gfx::Rect& cached_screen_bounds,
1004     const gfx::Rect& current_screen_bounds,
1005     const gfx::Size& minimum_size,
1006     gfx::Rect* bounds) const {
1007   *bounds = cached_bounds;
1008 
1009   // Reposition and resize the bounds if the cached_screen_bounds is different
1010   // from the current screen bounds and the current screen bounds doesn't
1011   // completely contain the bounds.
1012   if (cached_screen_bounds != current_screen_bounds &&
1013       !current_screen_bounds.Contains(cached_bounds)) {
1014     bounds->set_width(
1015         std::max(minimum_size.width(),
1016                  std::min(bounds->width(), current_screen_bounds.width())));
1017     bounds->set_height(
1018         std::max(minimum_size.height(),
1019                  std::min(bounds->height(), current_screen_bounds.height())));
1020     bounds->set_x(
1021         std::max(current_screen_bounds.x(),
1022                  std::min(bounds->x(),
1023                           current_screen_bounds.right() - bounds->width())));
1024     bounds->set_y(
1025         std::max(current_screen_bounds.y(),
1026                  std::min(bounds->y(),
1027                           current_screen_bounds.bottom() - bounds->height())));
1028   }
1029 }
1030 
LoadDefaults(CreateParams params) const1031 AppWindow::CreateParams AppWindow::LoadDefaults(CreateParams params)
1032     const {
1033   // Ensure width and height are specified.
1034   if (params.content_spec.bounds.width() == 0 &&
1035       params.window_spec.bounds.width() == 0) {
1036     params.content_spec.bounds.set_width(kDefaultWidth);
1037   }
1038   if (params.content_spec.bounds.height() == 0 &&
1039       params.window_spec.bounds.height() == 0) {
1040     params.content_spec.bounds.set_height(kDefaultHeight);
1041   }
1042 
1043   // If left and top are left undefined, the native app window will center
1044   // the window on the main screen in a platform-defined manner.
1045 
1046   // Load cached state if it exists.
1047   if (!params.window_key.empty()) {
1048     AppWindowGeometryCache* cache =
1049         AppWindowGeometryCache::Get(browser_context());
1050 
1051     gfx::Rect cached_bounds;
1052     gfx::Rect cached_screen_bounds;
1053     ui::WindowShowState cached_state = ui::SHOW_STATE_DEFAULT;
1054     if (cache->GetGeometry(extension_id(),
1055                            params.window_key,
1056                            &cached_bounds,
1057                            &cached_screen_bounds,
1058                            &cached_state)) {
1059       // App window has cached screen bounds, make sure it fits on screen in
1060       // case the screen resolution changed.
1061       display::Screen* screen = display::Screen::GetScreen();
1062       display::Display display = screen->GetDisplayMatching(cached_bounds);
1063       gfx::Rect current_screen_bounds = display.work_area();
1064       SizeConstraints constraints(params.GetWindowMinimumSize(gfx::Insets()),
1065                                   params.GetWindowMaximumSize(gfx::Insets()));
1066       AdjustBoundsToBeVisibleOnScreen(cached_bounds,
1067                                       cached_screen_bounds,
1068                                       current_screen_bounds,
1069                                       constraints.GetMinimumSize(),
1070                                       &params.window_spec.bounds);
1071       params.state = cached_state;
1072 
1073       // Since we are restoring a cached state, reset the content bounds spec to
1074       // ensure it is not used.
1075       params.content_spec.ResetBounds();
1076     }
1077   }
1078 
1079   return params;
1080 }
1081 
1082 // static
RawDraggableRegionsToSkRegion(const std::vector<DraggableRegion> & regions)1083 SkRegion* AppWindow::RawDraggableRegionsToSkRegion(
1084     const std::vector<DraggableRegion>& regions) {
1085   SkRegion* sk_region = new SkRegion;
1086   for (auto iter = regions.cbegin(); iter != regions.cend(); ++iter) {
1087     const DraggableRegion& region = *iter;
1088     sk_region->op(
1089         SkIRect::MakeLTRB(region.bounds.x(), region.bounds.y(),
1090                           region.bounds.right(), region.bounds.bottom()),
1091         region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
1092   }
1093   return sk_region;
1094 }
1095 
1096 }  // namespace extensions
1097