File: discard_before_unload_helper.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 (120 lines) | stat: -rw-r--r-- 4,766 bytes parent folder | download | duplicates (7)
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
// Copyright 2018 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/resource_coordinator/discard_before_unload_helper.h"

#include "base/functional/bind.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"

namespace resource_coordinator {

namespace {

// This is a helper class that determines whether or not there is a beforeunload
// handler associated with a given WebContents, and if so, whether or not it
// indicates that the page contains unsaved state.
//
// This is done by actually running the beforeunload handler if there is one. If
// the beforeunload handler returns a non-empty string then a javascript dialog
// request is made, in which case it is intercepted before it is displayed and
// implicitly canceled.
//
// The beforeunload is initiated via WebContents::DispatchBeforeUnload, and the
// outcome of the beforeunload is monitored via
// WebContentsObserver::BeforeUnloadFired and ::BeforeUnloadDialogCanceled.
//
// Note that the callback is guaranteed to be invoked; in the worst case
// scenario it will be invoked when the WebContents is destroyed, with a
// |proceed|=true value.
class DiscardBeforeUnloadHelper : public content::WebContentsObserver {
 public:
  static void HasBeforeUnloadHandler(content::WebContents* contents,
                                     HasBeforeUnloadHandlerCallback&& callback);

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

  ~DiscardBeforeUnloadHelper() override;

 private:
  // This is only meant to be called via "new", as the object takes ownership
  // of itself.
  DiscardBeforeUnloadHelper(content::WebContents* contents,
                            HasBeforeUnloadHandlerCallback&& callback);

  // WebContentsObserver:
  void BeforeUnloadFired(bool proceed) override;
  void BeforeUnloadDialogCancelled() override;
  void WebContentsDestroyed() override;

  // Responds by invoking the callback, and cleaning itself up.
  void Respond(bool has_beforeunload_handler);

  // This object keeps itself alive while waiting for a callback, and cleans
  // itself up when done.
  std::unique_ptr<DiscardBeforeUnloadHelper> self_;

  HasBeforeUnloadHandlerCallback callback_;
};

void DiscardBeforeUnloadHelper::HasBeforeUnloadHandler(
    content::WebContents* contents,
    HasBeforeUnloadHandlerCallback&& callback) {
  // Create this object and deliberately let go of it. It deletes itself after
  // it has invoked the callback.
  new DiscardBeforeUnloadHelper(contents, std::move(callback));
}

DiscardBeforeUnloadHelper::DiscardBeforeUnloadHelper(
    content::WebContents* contents,
    HasBeforeUnloadHandlerCallback&& callback)
    : WebContentsObserver(contents),
      self_(this),
      callback_(std::move(callback)) {
  DCHECK(!callback_.is_null());
  // NOTE: Ideally this would call NeedToFireBeforeUnloadOrUnloadEvents and
  // entirely skip on the dispatch if there are no unload handlers installed.
  // Unfortunately, NeedToFireBeforeUnloadOrUnloadEvents doesn't check the
  // main frame, so this doesn't quite work. See this related bug for more
  // information: crbug.com/869956
  web_contents()->DispatchBeforeUnload(true /* auto_cancel */);
}

DiscardBeforeUnloadHelper::~DiscardBeforeUnloadHelper() = default;

void DiscardBeforeUnloadHelper::BeforeUnloadFired(bool proceed) {
  // |proceed = true| means no beforeunload handler and vice-versa.
  Respond(!proceed /* has_beforeunload_handler */);
}

void DiscardBeforeUnloadHelper::BeforeUnloadDialogCancelled() {
  // Canceling a beforeunload dialog means that there was a beforeunload handler
  // that was already running prior to our request, and more specifically that
  // the user wants to stay on the page.
  Respond(true /* has_beforeunload_handler */);
}

void DiscardBeforeUnloadHelper::WebContentsDestroyed() {
  // If a WebContents is destroyed while waiting for the beforeunload response
  // this can be interpreted as |proceed| being true, as the page is no longer
  // going to be around to be preserved.
  Respond(false /* has_beforeunload_handler */);
}

void DiscardBeforeUnloadHelper::Respond(bool has_beforeunload_handler) {
  std::move(callback_).Run(has_beforeunload_handler);
  self_.reset();
}

}  // namespace

void HasBeforeUnloadHandler(content::WebContents* contents,
                            HasBeforeUnloadHandlerCallback&& callback) {
  DiscardBeforeUnloadHelper::HasBeforeUnloadHandler(contents,
                                                    std::move(callback));
}

}  // namespace resource_coordinator