Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / api / tab_capture / offscreen_presentation.cc
blob2ccd614b8035321582aac9c7c12777da22d06bdf
1 // Copyright 2015 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/tab_capture/offscreen_presentation.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/web_contents_sizer.h"
13 #include "content/public/browser/render_widget_host_view.h"
14 #include "content/public/browser/web_contents.h"
15 #include "extensions/browser/extension_host.h"
16 #include "extensions/browser/process_manager.h"
18 #if defined(USE_AURA)
19 #include "base/memory/weak_ptr.h"
20 #include "base/thread_task_runner_handle.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_list.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "ui/aura/window.h"
25 #include "ui/gfx/native_widget_types.h"
26 #endif
28 using content::WebContents;
30 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::OffscreenPresentationsOwner);
32 namespace {
34 // Upper limit on the number of simultaneous off-screen presentations per
35 // extension instance.
36 const int kMaxPresentationsPerExtension = 3;
38 // Time intervals used by the logic that detects when the capture of a
39 // presentation has stopped, to automatically tear it down and free resources.
40 const int kMaxSecondsToWaitForCapture = 60;
41 const int kPollIntervalInSeconds = 1;
43 #if defined(USE_AURA)
45 // A WindowObserver that automatically finds a root Window to adopt the
46 // WebContents native view containing the OffscreenPresentation content. This
47 // is a workaround for Aura, which requires the WebContents native view be
48 // attached somewhere in the window tree in order to gain access to the
49 // compositing and capture functionality. The WebContents native view, although
50 // attached to the window tree, will never become visible on-screen.
51 class WindowAdoptionAgent : protected aura::WindowObserver {
52 public:
53 static void Start(aura::Window* offscreen_window) {
54 new WindowAdoptionAgent(offscreen_window);
55 // WindowAdoptionAgent destroys itself when the Window calls
56 // OnWindowDestroyed().
59 protected:
60 void OnWindowParentChanged(aura::Window* target,
61 aura::Window* parent) final {
62 if (target != offscreen_window_ || parent != nullptr)
63 return;
65 // Post a task to return to the event loop before finding a new parent, to
66 // avoid clashing with the currently-in-progress window tree hierarchy
67 // changes.
68 base::ThreadTaskRunnerHandle::Get()->PostTask(
69 FROM_HERE,
70 base::Bind(&WindowAdoptionAgent::FindNewParent,
71 weak_ptr_factory_.GetWeakPtr()));
74 void OnWindowDestroyed(aura::Window* window) final {
75 delete this;
78 private:
79 explicit WindowAdoptionAgent(aura::Window* offscreen_window)
80 : offscreen_window_(offscreen_window),
81 weak_ptr_factory_(this) {
82 DVLOG(2) << "WindowAdoptionAgent for offscreen window " << offscreen_window_
83 << " is being created.";
84 offscreen_window->AddObserver(this);
85 OnWindowParentChanged(offscreen_window_, offscreen_window_->parent());
88 ~WindowAdoptionAgent() final {
89 DVLOG(2) << "WindowAdoptionAgent for offscreen window " << offscreen_window_
90 << " is self-destructing.";
93 void FindNewParent() {
94 BrowserList* const browsers =
95 BrowserList::GetInstance(chrome::GetActiveDesktop());
96 Browser* const active_browser =
97 browsers ? browsers->GetLastActive() : nullptr;
98 BrowserWindow* const active_window =
99 active_browser ? active_browser->window() : nullptr;
100 aura::Window* const native_window =
101 active_window ? active_window->GetNativeWindow() : nullptr;
102 aura::Window* const root_window =
103 native_window ? native_window->GetRootWindow() : nullptr;
104 if (root_window) {
105 DVLOG(2) << "Root window " << root_window
106 << " adopts the offscreen window " << offscreen_window_ << '.';
107 root_window->AddChild(offscreen_window_);
108 } else {
109 LOG(DFATAL) << "Unable to find an aura root window. "
110 "OffscreenPresentation compositing may be halted!";
114 aura::Window* const offscreen_window_;
115 base::WeakPtrFactory<WindowAdoptionAgent> weak_ptr_factory_;
117 DISALLOW_COPY_AND_ASSIGN(WindowAdoptionAgent);
120 #endif // USE_AURA
122 } // namespace
124 namespace extensions {
126 OffscreenPresentationsOwner::OffscreenPresentationsOwner(WebContents* contents)
127 : extension_web_contents_(contents) {
128 DCHECK(extension_web_contents_);
131 OffscreenPresentationsOwner::~OffscreenPresentationsOwner() {}
133 // static
134 OffscreenPresentationsOwner* OffscreenPresentationsOwner::Get(
135 content::WebContents* extension_web_contents) {
136 // CreateForWebContents() really means "create if not exists."
137 CreateForWebContents(extension_web_contents);
138 return FromWebContents(extension_web_contents);
141 OffscreenPresentation* OffscreenPresentationsOwner::FindOrStartPresentation(
142 const GURL& start_url,
143 const std::string& presentation_id,
144 const gfx::Size& initial_size) {
145 OffscreenPresentation* presentation =
146 FindPresentation(start_url, presentation_id);
147 if (presentation) {
148 DVLOG(1) << "Returning already-running OffscreenPresentation for start_url="
149 << presentation->start_url();
150 return presentation;
153 if (presentations_.size() >= kMaxPresentationsPerExtension)
154 return nullptr; // Maximum number of presentations reached.
156 presentation = new OffscreenPresentation(this, start_url, presentation_id);
157 presentations_.push_back(presentation);
158 presentation->Start(initial_size);
159 return presentation;
162 void OffscreenPresentationsOwner::ClosePresentation(
163 OffscreenPresentation* presentation) {
164 const auto it =
165 std::find(presentations_.begin(), presentations_.end(), presentation);
166 if (it != presentations_.end())
167 presentations_.erase(it);
170 OffscreenPresentation* OffscreenPresentationsOwner::FindPresentation(
171 const GURL& start_url, const std::string& presentation_id) const {
172 for (OffscreenPresentation* presentation : presentations_) {
173 if (presentation->start_url() == start_url &&
174 presentation->presentation_id() == presentation_id)
175 return presentation;
177 return nullptr;
180 OffscreenPresentation::OffscreenPresentation(OffscreenPresentationsOwner* owner,
181 const GURL& start_url,
182 const std::string& id)
183 : owner_(owner),
184 start_url_(start_url),
185 presentation_id_(id),
186 profile_(Profile::FromBrowserContext(
187 owner->extension_web_contents()->GetBrowserContext())
188 ->CreateOffTheRecordProfile()),
189 capture_poll_timer_(false, false),
190 content_capture_was_detected_(false) {
191 DCHECK(profile_);
194 OffscreenPresentation::~OffscreenPresentation() {
195 DVLOG(1) << "Destroying OffscreenPresentation for start_url="
196 << start_url_.spec();
199 void OffscreenPresentation::Start(const gfx::Size& initial_size) {
200 DCHECK(start_time_.is_null());
201 DVLOG(1) << "Starting OffscreenPresentation with initial size of "
202 << initial_size.ToString() << " for start_url=" << start_url_.spec();
204 // Create the WebContents to contain the off-screen presentation page.
205 presentation_web_contents_.reset(
206 WebContents::Create(WebContents::CreateParams(profile_.get())));
207 presentation_web_contents_->SetDelegate(this);
208 WebContentsObserver::Observe(presentation_web_contents_.get());
210 #if defined(USE_AURA)
211 WindowAdoptionAgent::Start(presentation_web_contents_->GetNativeView());
212 #endif
214 // Set initial size, if specified.
215 if (!initial_size.IsEmpty())
216 ResizeWebContents(presentation_web_contents_.get(), initial_size);
218 // Mute audio output. When tab capture starts, the audio will be
219 // automatically unmuted, but will be captured into the MediaStream.
220 presentation_web_contents_->SetAudioMuted(true);
222 // Navigate to the initial URL of the presentation.
223 content::NavigationController::LoadURLParams load_params(start_url_);
224 load_params.should_replace_current_entry = true;
225 load_params.should_clear_history_list = true;
226 presentation_web_contents_->GetController().LoadURLWithParams(load_params);
228 start_time_ = base::TimeTicks::Now();
229 DieIfContentCaptureEnded();
232 void OffscreenPresentation::CloseContents(WebContents* source) {
233 DCHECK_EQ(presentation_web_contents_.get(), source);
234 // Javascript in the page called window.close().
235 DVLOG(1) << "OffscreenPresentation will die at renderer's request for "
236 "start_url=" << start_url_.spec();
237 owner_->ClosePresentation(this);
240 bool OffscreenPresentation::ShouldSuppressDialogs(WebContents* source) {
241 DCHECK_EQ(presentation_web_contents_.get(), source);
242 // Suppress all because there is no possible direct user interaction with
243 // dialogs.
244 return true;
247 bool OffscreenPresentation::ShouldFocusLocationBarByDefault(
248 WebContents* source) {
249 DCHECK_EQ(presentation_web_contents_.get(), source);
250 // Indicate the location bar should be focused instead of the page, even
251 // though there is no location bar. This will prevent the page from
252 // automatically receiving input focus, which should never occur since there
253 // is not supposed to be any direct user interaction.
254 return true;
257 bool OffscreenPresentation::ShouldFocusPageAfterCrash() {
258 // Never focus the page. Not even after a crash.
259 return false;
262 void OffscreenPresentation::CanDownload(
263 const GURL& url,
264 const std::string& request_method,
265 const base::Callback<void(bool)>& callback) {
266 // Presentation pages are not allowed to download files.
267 callback.Run(false);
270 bool OffscreenPresentation::HandleContextMenu(
271 const content::ContextMenuParams& params) {
272 // Context menus should never be shown. Do nothing, but indicate the context
273 // menu was shown so that default implementation in libcontent does not
274 // attempt to do so on its own.
275 return true;
278 bool OffscreenPresentation::PreHandleKeyboardEvent(
279 WebContents* source,
280 const content::NativeWebKeyboardEvent& event,
281 bool* is_keyboard_shortcut) {
282 DCHECK_EQ(presentation_web_contents_.get(), source);
283 // Intercept and silence all keyboard events before they can be sent to the
284 // renderer.
285 *is_keyboard_shortcut = false;
286 return true;
289 bool OffscreenPresentation::PreHandleGestureEvent(
290 WebContents* source,
291 const blink::WebGestureEvent& event) {
292 DCHECK_EQ(presentation_web_contents_.get(), source);
293 // Intercept and silence all gesture events before they can be sent to the
294 // renderer.
295 return true;
298 bool OffscreenPresentation::CanDragEnter(
299 WebContents* source,
300 const content::DropData& data,
301 blink::WebDragOperationsMask operations_allowed) {
302 DCHECK_EQ(presentation_web_contents_.get(), source);
303 // Halt all drag attempts onto the page since there should be no direct user
304 // interaction with it.
305 return false;
308 bool OffscreenPresentation::ShouldCreateWebContents(
309 WebContents* contents,
310 int route_id,
311 int main_frame_route_id,
312 WindowContainerType window_container_type,
313 const std::string& frame_name,
314 const GURL& target_url,
315 const std::string& partition_id,
316 content::SessionStorageNamespace* session_storage_namespace) {
317 DCHECK_EQ(presentation_web_contents_.get(), contents);
318 // Disallow creating separate WebContentses. The WebContents implementation
319 // uses this to spawn new windows/tabs, which is also not allowed for
320 // presentation pages.
321 return false;
324 bool OffscreenPresentation::EmbedsFullscreenWidget() const {
325 // OffscreenPresentation will manage fullscreen widgets.
326 return true;
329 void OffscreenPresentation::EnterFullscreenModeForTab(WebContents* contents,
330 const GURL& origin) {
331 DCHECK_EQ(presentation_web_contents_.get(), contents);
333 if (in_fullscreen_mode())
334 return;
336 // TODO(miu): Refine fullscreen handling behavior once the Presentation API
337 // spec group defines this behavior.
339 non_fullscreen_size_ =
340 contents->GetRenderWidgetHostView()->GetViewBounds().size();
341 if (contents->GetCapturerCount() >= 0 &&
342 !contents->GetPreferredSize().IsEmpty()) {
343 ResizeWebContents(contents, contents->GetPreferredSize());
347 void OffscreenPresentation::ExitFullscreenModeForTab(WebContents* contents) {
348 DCHECK_EQ(presentation_web_contents_.get(), contents);
350 if (!in_fullscreen_mode())
351 return;
353 ResizeWebContents(contents, non_fullscreen_size_);
354 non_fullscreen_size_ = gfx::Size();
357 bool OffscreenPresentation::IsFullscreenForTabOrPending(
358 const WebContents* contents) const {
359 DCHECK_EQ(presentation_web_contents_.get(), contents);
360 return in_fullscreen_mode();
363 blink::WebDisplayMode OffscreenPresentation::GetDisplayMode(
364 const WebContents* contents) const {
365 DCHECK_EQ(presentation_web_contents_.get(), contents);
366 return in_fullscreen_mode() ?
367 blink::WebDisplayModeFullscreen : blink::WebDisplayModeBrowser;
370 void OffscreenPresentation::RequestMediaAccessPermission(
371 WebContents* contents,
372 const content::MediaStreamRequest& request,
373 const content::MediaResponseCallback& callback) {
374 DCHECK_EQ(presentation_web_contents_.get(), contents);
376 // This method is being called to check whether an extension is permitted to
377 // capture the page. Verify that the request is being made by the extension
378 // that spawned this OffscreenPresentation.
380 // Find the extension ID associated with the extension background page's
381 // WebContents.
382 content::BrowserContext* const extension_browser_context =
383 owner_->extension_web_contents()->GetBrowserContext();
384 std::string extension_id;
385 for (const ExtensionHost* host :
386 ProcessManager::Get(extension_browser_context)->background_hosts()) {
387 if (host->host_contents() == owner_->extension_web_contents()) {
388 extension_id = host->extension_id();
389 break;
393 // If verified, allow any tab capture audio/video devices that were requested.
394 extensions::TabCaptureRegistry* const tab_capture_registry =
395 extensions::TabCaptureRegistry::Get(extension_browser_context);
396 content::MediaStreamDevices devices;
397 if (tab_capture_registry && tab_capture_registry->VerifyRequest(
398 request.render_process_id,
399 request.render_frame_id,
400 extension_id)) {
401 if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) {
402 devices.push_back(content::MediaStreamDevice(
403 content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string()));
405 if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) {
406 devices.push_back(content::MediaStreamDevice(
407 content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
411 DVLOG(2) << "Allowing " << devices.size()
412 << " capture devices for OffscreenPresentation content.";
414 callback.Run(
415 devices,
416 devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE :
417 content::MEDIA_DEVICE_OK,
418 scoped_ptr<content::MediaStreamUI>(nullptr));
421 bool OffscreenPresentation::CheckMediaAccessPermission(
422 WebContents* contents,
423 const GURL& security_origin,
424 content::MediaStreamType type) {
425 DCHECK_EQ(presentation_web_contents_.get(), contents);
426 return type == content::MEDIA_TAB_AUDIO_CAPTURE ||
427 type == content::MEDIA_TAB_VIDEO_CAPTURE;
430 void OffscreenPresentation::DidShowFullscreenWidget(int routing_id) {
431 if (presentation_web_contents_->GetCapturerCount() == 0 ||
432 presentation_web_contents_->GetPreferredSize().IsEmpty())
433 return; // Do nothing, since no preferred size is specified.
434 content::RenderWidgetHostView* const current_fs_view =
435 presentation_web_contents_->GetFullscreenRenderWidgetHostView();
436 if (current_fs_view)
437 current_fs_view->SetSize(presentation_web_contents_->GetPreferredSize());
440 void OffscreenPresentation::DieIfContentCaptureEnded() {
441 DCHECK(presentation_web_contents_.get());
443 if (content_capture_was_detected_) {
444 if (presentation_web_contents_->GetCapturerCount() == 0) {
445 DVLOG(2) << "Capture of OffscreenPresentation content has stopped for "
446 "start_url=" << start_url_.spec();
447 owner_->ClosePresentation(this);
448 return;
449 } else {
450 DVLOG(3) << "Capture of OffscreenPresentation content continues for "
451 "start_url=" << start_url_.spec();
453 } else if (presentation_web_contents_->GetCapturerCount() > 0) {
454 DVLOG(2) << "Capture of OffscreenPresentation content has started for "
455 "start_url=" << start_url_.spec();
456 content_capture_was_detected_ = true;
457 } else if (base::TimeTicks::Now() - start_time_ >
458 base::TimeDelta::FromSeconds(kMaxSecondsToWaitForCapture)) {
459 // More than a minute has elapsed since this OffscreenPresentation was
460 // started and content capture still hasn't started. As a safety
461 // precaution, assume that content capture is never going to start and die
462 // to free up resources.
463 LOG(WARNING) << "Capture of OffscreenPresentation content did not start "
464 "within timeout for start_url=" << start_url_.spec();
465 owner_->ClosePresentation(this);
466 return;
469 // Schedule the timer to check again in a second.
470 capture_poll_timer_.Start(
471 FROM_HERE,
472 base::TimeDelta::FromSeconds(kPollIntervalInSeconds),
473 base::Bind(&OffscreenPresentation::DieIfContentCaptureEnded,
474 base::Unretained(this)));
477 } // namespace extensions