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
|