File: offscreen_tab.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (400 lines) | stat: -rw-r--r-- 15,677 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/media/offscreen_tab.h"

#include <algorithm>
#include <utility>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_destroyer.h"
#include "components/media_router/browser/presentation/presentation_navigation_policy.h"
#include "components/media_router/browser/presentation/receiver_presentation_service_delegate_impl.h"  // nogncheck
#include "content/public/browser/keyboard_event_processing_result.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/presentation_receiver_flags.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"

#if defined(USE_AURA)
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#endif  // defined(USE_AURA)

using content::WebContents;

namespace {

// Time intervals used by the logic that detects when the capture of an
// offscreen tab has stopped, to automatically tear it down and free resources.
constexpr base::TimeDelta kMaxWaitForCapture = base::Minutes(1);
constexpr base::TimeDelta kPollInterval = base::Seconds(1);

}  // namespace

#if defined(USE_AURA)
// A WindowObserver that automatically finds a root Window to adopt the
// WebContents native view containing the tab content being streamed, when the
// native view is offscreen, or gets detached from the aura window tree. This is
// a workaround for Aura, which requires the WebContents native view be attached
// somewhere in the window tree in order to gain access to the compositing and
// capture functionality. The WebContents native view, although attached to the
// window tree, does not become visible on-screen (until it is properly made
// visible by the user, for example by switching to the tab).
class OffscreenTab::WindowAdoptionAgent final : protected aura::WindowObserver {
 public:
  explicit WindowAdoptionAgent(aura::Window* content_window)
      : content_window_(content_window) {
    if (content_window_) {
      content_window->AddObserver(this);
      ScheduleFindNewParentIfDetached(content_window_->GetRootWindow());
    }
  }

  WindowAdoptionAgent(const WindowAdoptionAgent&) = delete;
  WindowAdoptionAgent& operator=(const WindowAdoptionAgent&) = delete;

  ~WindowAdoptionAgent() final {
    if (content_window_)
      content_window_->RemoveObserver(this);
  }

 protected:
  void ScheduleFindNewParentIfDetached(aura::Window* root_window) {
    if (root_window)
      return;
    // Post a task to return to the event loop before finding a new parent, to
    // avoid clashing with the currently-in-progress window tree hierarchy
    // changes.
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&WindowAdoptionAgent::FindNewParent,
                                  weak_ptr_factory_.GetWeakPtr()));
  }

  // aura::WindowObserver implementation.
  void OnWindowDestroyed(aura::Window* window) final {
    DCHECK_EQ(content_window_, window);
    content_window_ = nullptr;
  }

  void OnWindowRemovingFromRootWindow(aura::Window* window,
                                      aura::Window* new_root) final {
    ScheduleFindNewParentIfDetached(new_root);
  }

 private:
  void FindNewParent() {
    // The window may have been destroyed by the time this is reached.
    if (!content_window_)
      return;
    // If the window has already been attached to a root window, then it's not
    // necessary to find a new parent.
    if (content_window_->GetRootWindow())
      return;
    BrowserList* const browsers = BrowserList::GetInstance();
    Browser* const active_browser =
        browsers ? browsers->GetLastActive() : nullptr;
    BrowserWindow* const active_window =
        active_browser ? active_browser->window() : nullptr;
    aura::Window* const native_window =
        active_window ? active_window->GetNativeWindow() : nullptr;
    aura::Window* const root_window =
        native_window ? native_window->GetRootWindow() : nullptr;
    if (root_window) {
      DVLOG(2) << "Root window " << root_window << " adopts the content window "
               << content_window_ << '.';
      root_window->AddChild(content_window_);
    } else {
      LOG(WARNING) << "Unable to find an aura root window.  "
                      "Compositing of the content may be halted!";
    }
  }

  raw_ptr<aura::Window> content_window_;
  base::WeakPtrFactory<WindowAdoptionAgent> weak_ptr_factory_{this};
};
#endif  // defined(USE_AURA)

OffscreenTab::OffscreenTab(Owner* owner, content::BrowserContext* context)
    : owner_(owner),
      otr_profile_(Profile::FromBrowserContext(context)->GetOffTheRecordProfile(
          Profile::OTRProfileID::CreateUniqueForMediaRouter(),
          /*create_if_needed=*/true)),
      content_capture_was_detected_(false),
      navigation_policy_(
          std::make_unique<media_router::DefaultNavigationPolicy>()) {
  DCHECK(owner_);
  otr_profile_->AddObserver(this);
}

OffscreenTab::~OffscreenTab() {
  DVLOG(1) << "Destroying OffscreenTab for start_url=" << start_url_.spec();
  if (otr_profile_) {
    otr_profile_->RemoveObserver(this);
    ProfileDestroyer::DestroyOTRProfileWhenAppropriate(otr_profile_);
  }
}

void OffscreenTab::Start(const GURL& start_url,
                         const gfx::Size& initial_size,
                         const std::string& optional_presentation_id) {
  DCHECK(start_time_.is_null());
  start_url_ = start_url;
  DVLOG(1) << "Starting OffscreenTab with initial size of "
           << initial_size.ToString() << " for start_url=" << start_url_.spec();

  // Create the WebContents to contain the off-screen tab's page.
  WebContents::CreateParams params(otr_profile_);
  if (!optional_presentation_id.empty())
    params.starting_sandbox_flags = content::kPresentationReceiverSandboxFlags;

  offscreen_tab_web_contents_ = WebContents::Create(params);
  offscreen_tab_web_contents_->SetOwnerLocationForDebug(FROM_HERE);
  offscreen_tab_web_contents_->SetDelegate(this);
  WebContentsObserver::Observe(offscreen_tab_web_contents_.get());

#if defined(USE_AURA)
  window_agent_ = std::make_unique<WindowAdoptionAgent>(
      offscreen_tab_web_contents_->GetNativeView());
#endif

  // Set initial size, if specified.
  if (!initial_size.IsEmpty()) {
    offscreen_tab_web_contents_.get()->Resize(gfx::Rect(initial_size));
  }

  // Mute audio output.  When tab capture starts, the audio will be
  // automatically unmuted, but will be captured into the MediaStream.
  offscreen_tab_web_contents_->SetAudioMuted(true);

  if (!optional_presentation_id.empty()) {
    // This offscreen tab is a presentation created through the Presentation
    // API. https://www.w3.org/TR/presentation-api/
    //
    // Create a ReceiverPresentationServiceDelegateImpl associated with the
    // offscreen tab's WebContents.  The new instance will set up the necessary
    // infrastructure to allow controlling pages the ability to connect to the
    // offscreen tab.
    DVLOG(1) << "Register with ReceiverPresentationServiceDelegateImpl, "
             << "presentation_id=" << optional_presentation_id;
    media_router::ReceiverPresentationServiceDelegateImpl::CreateForWebContents(
        offscreen_tab_web_contents_.get(), optional_presentation_id);

    // Presentations are not allowed to perform top-level navigations after
    // initial load.  This is enforced through sandboxing flags, but we also
    // enforce it here.
    navigation_policy_ =
        std::make_unique<media_router::PresentationNavigationPolicy>();
  }

  // Navigate to the initial URL.
  content::NavigationController::LoadURLParams load_params(start_url_);
  load_params.should_replace_current_entry = true;
  load_params.should_clear_history_list = true;
  offscreen_tab_web_contents_->GetController().LoadURLWithParams(load_params);

  start_time_ = base::TimeTicks::Now();
  DieIfContentCaptureEnded();
}

void OffscreenTab::Close() {
  if (offscreen_tab_web_contents_)
    offscreen_tab_web_contents_->ClosePage();
}

void OffscreenTab::CloseContents(WebContents* source) {
  DCHECK_EQ(offscreen_tab_web_contents_.get(), source);
  // Javascript in the page called window.close().
  DVLOG(1) << "OffscreenTab for start_url=" << start_url_.spec() << " will die";
  owner_->DestroyTab(this);
  // |this| is no longer valid.
}

bool OffscreenTab::ShouldSuppressDialogs(WebContents* source) {
  DCHECK_EQ(offscreen_tab_web_contents_.get(), source);
  // Suppress all because there is no possible direct user interaction with
  // dialogs.
  // TODO(crbug.com/40526231): This does not suppress window.print().
  return true;
}

bool OffscreenTab::ShouldFocusLocationBarByDefault(WebContents* source) {
  DCHECK_EQ(offscreen_tab_web_contents_.get(), source);
  // Indicate the location bar should be focused instead of the page, even
  // though there is no location bar.  This will prevent the page from
  // automatically receiving input focus, which should never occur since there
  // is not supposed to be any direct user interaction.
  return true;
}

bool OffscreenTab::ShouldFocusPageAfterCrash(content::WebContents* source) {
  // Never focus the page.  Not even after a crash.
  return false;
}

void OffscreenTab::CanDownload(const GURL& url,
                               const std::string& request_method,
                               base::OnceCallback<void(bool)> callback) {
  // Offscreen tab pages are not allowed to download files.
  std::move(callback).Run(false);
}

bool OffscreenTab::HandleContextMenu(
    content::RenderFrameHost& render_frame_host,
    const content::ContextMenuParams& params) {
  // Context menus should never be shown.  Do nothing, but indicate the context
  // menu was shown so that default implementation in libcontent does not
  // attempt to do so on its own.
  return true;
}

content::KeyboardEventProcessingResult OffscreenTab::PreHandleKeyboardEvent(
    WebContents* source,
    const input::NativeWebKeyboardEvent& event) {
  DCHECK_EQ(offscreen_tab_web_contents_.get(), source);
  // Intercept and silence all keyboard events before they can be sent to the
  // renderer.
  return content::KeyboardEventProcessingResult::HANDLED;
}

bool OffscreenTab::PreHandleGestureEvent(WebContents* source,
                                         const blink::WebGestureEvent& event) {
  DCHECK_EQ(offscreen_tab_web_contents_.get(), source);
  // Intercept and silence all gesture events before they can be sent to the
  // renderer.
  return true;
}

bool OffscreenTab::CanDragEnter(WebContents* source,
                                const content::DropData& data,
                                blink::DragOperationsMask operations_allowed) {
  DCHECK_EQ(offscreen_tab_web_contents_.get(), source);
  // Halt all drag attempts onto the page since there should be no direct user
  // interaction with it.
  return false;
}

bool OffscreenTab::IsWebContentsCreationOverridden(
    content::RenderFrameHost* opener,
    content::SiteInstance* source_site_instance,
    content::mojom::WindowContainerType window_container_type,
    const GURL& opener_url,
    const std::string& frame_name,
    const GURL& target_url) {
  // Disallow creating separate WebContentses.  The WebContents implementation
  // uses this to spawn new windows/tabs, which is also not allowed for
  // offscreen tabs.
  return true;
}

void OffscreenTab::EnterFullscreenModeForTab(
    content::RenderFrameHost* requesting_frame,
    const blink::mojom::FullscreenOptions& options) {
  auto* contents = WebContents::FromRenderFrameHost(requesting_frame);
  DCHECK_EQ(offscreen_tab_web_contents_.get(), contents);

  if (in_fullscreen_mode())
    return;

  non_fullscreen_size_ =
      contents->GetRenderWidgetHostView()->GetViewBounds().size();
  if (contents->IsBeingCaptured() && !contents->GetPreferredSize().IsEmpty())
    contents->Resize(gfx::Rect(contents->GetPreferredSize()));
}

void OffscreenTab::ExitFullscreenModeForTab(WebContents* contents) {
  DCHECK_EQ(offscreen_tab_web_contents_.get(), contents);

  if (!in_fullscreen_mode())
    return;

  contents->Resize(gfx::Rect(non_fullscreen_size_));
  non_fullscreen_size_ = gfx::Size();
}

bool OffscreenTab::IsFullscreenForTabOrPending(const WebContents* contents) {
  DCHECK_EQ(offscreen_tab_web_contents_.get(), contents);
  return in_fullscreen_mode();
}

blink::mojom::DisplayMode OffscreenTab::GetDisplayMode(
    const WebContents* contents) {
  DCHECK_EQ(offscreen_tab_web_contents_.get(), contents);
  return in_fullscreen_mode() ? blink::mojom::DisplayMode::kFullscreen
                              : blink::mojom::DisplayMode::kBrowser;
}

void OffscreenTab::RequestMediaAccessPermission(
    WebContents* contents,
    const content::MediaStreamRequest& request,
    content::MediaResponseCallback callback) {
  std::move(callback).Run(blink::mojom::StreamDevicesSet(),
                          blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED,
                          nullptr);
}

bool OffscreenTab::CheckMediaAccessPermission(
    content::RenderFrameHost* render_frame_host,
    const url::Origin& security_origin,
    blink::mojom::MediaStreamType type) {
  return false;
}

void OffscreenTab::DidStartNavigation(
    content::NavigationHandle* navigation_handle) {
  DCHECK(offscreen_tab_web_contents_.get());
  if (!navigation_policy_->AllowNavigation(navigation_handle)) {
    DVLOG(2) << "Closing because NavigationPolicy disallowed "
             << "StartNavigation to " << navigation_handle->GetURL().spec();
    Close();
  }
}

void OffscreenTab::DieIfContentCaptureEnded() {
  DCHECK(offscreen_tab_web_contents_.get());

  if (content_capture_was_detected_) {
    if (!offscreen_tab_web_contents_->IsBeingCaptured()) {
      DVLOG(2) << "Capture of OffscreenTab content has stopped for start_url="
               << start_url_.spec();
      owner_->DestroyTab(this);
      return;  // |this| is no longer valid.
    } else {
      DVLOG(3) << "Capture of OffscreenTab content continues for start_url="
               << start_url_.spec();
    }
  } else if (offscreen_tab_web_contents_->IsBeingCaptured()) {
    DVLOG(2) << "Capture of OffscreenTab content has started for start_url="
             << start_url_.spec();
    content_capture_was_detected_ = true;
  } else if (base::TimeTicks::Now() - start_time_ > kMaxWaitForCapture) {
    // More than a minute has elapsed since this OffscreenTab was started and
    // content capture still hasn't started.  As a safety precaution, assume
    // that content capture is never going to start and die to free up
    // resources.
    LOG(WARNING) << "Capture of OffscreenTab content did not start "
                 << "within timeout for start_url=" << start_url_.spec();
    owner_->DestroyTab(this);
    return;  // |this| is no longer valid.
  }

  // Schedule the timer to check again in a second.
  capture_poll_timer_.Start(
      FROM_HERE, kPollInterval,
      base::BindOnce(&OffscreenTab::DieIfContentCaptureEnded,
                     base::Unretained(this)));
}

void OffscreenTab::OnProfileWillBeDestroyed(Profile* profile) {
  DCHECK(profile == otr_profile_);
  otr_profile_ = nullptr;
  owner_->DestroyTab(this);
}