blob: 503f9aedda3b1b17eb6dc1a4a58572f01b6a5872 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/tabs/web_contents_closer.h"
#include <memory>
#include "chrome/browser/browser_shutdown.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
namespace {
// CloseTracker is used when closing a set of WebContents. It listens for
// deletions of the WebContents and removes from the internal set any time one
// is deleted.
class CloseTracker {
public:
using Contents = base::span<content::WebContents* const>;
explicit CloseTracker(const Contents& contents);
~CloseTracker();
// Returns true if there is another WebContents in the Tracker.
bool HasNext() const;
// Returns the next WebContents, or NULL if there are no more.
content::WebContents* Next();
private:
class DeletionObserver : public content::WebContentsObserver {
public:
DeletionObserver(CloseTracker* parent, content::WebContents* web_contents)
: WebContentsObserver(web_contents), parent_(parent) {}
private:
// WebContentsObserver:
void WebContentsDestroyed() override {
parent_->OnWebContentsDestroyed(this);
}
CloseTracker* parent_;
DISALLOW_COPY_AND_ASSIGN(DeletionObserver);
};
void OnWebContentsDestroyed(DeletionObserver* observer);
using Observers = std::vector<std::unique_ptr<DeletionObserver>>;
Observers observers_;
DISALLOW_COPY_AND_ASSIGN(CloseTracker);
};
CloseTracker::CloseTracker(const Contents& contents) {
observers_.reserve(contents.size());
for (content::WebContents* current : contents)
observers_.push_back(std::make_unique<DeletionObserver>(this, current));
}
CloseTracker::~CloseTracker() {
DCHECK(observers_.empty());
}
bool CloseTracker::HasNext() const {
return !observers_.empty();
}
content::WebContents* CloseTracker::Next() {
if (observers_.empty())
return nullptr;
DeletionObserver* observer = observers_[0].get();
content::WebContents* web_contents = observer->web_contents();
observers_.erase(observers_.begin());
return web_contents;
}
void CloseTracker::OnWebContentsDestroyed(DeletionObserver* observer) {
for (auto i = observers_.begin(); i != observers_.end(); ++i) {
if (observer == i->get()) {
observers_.erase(i);
return;
}
}
NOTREACHED() << "WebContents destroyed that wasn't in the list";
}
} // namespace
bool CloseWebContentses(WebContentsCloseDelegate* delegate,
base::span<content::WebContents* const> items,
uint32_t close_types) {
if (items.empty())
return true;
CloseTracker close_tracker(items);
// We only try the fast shutdown path if the whole browser process is *not*
// shutting down. Fast shutdown during browser termination is handled in
// browser_shutdown::OnShutdownStarting.
if (browser_shutdown::GetShutdownType() == browser_shutdown::NOT_VALID) {
// Construct a map of processes to the number of associated tabs that are
// closing.
base::flat_map<content::RenderProcessHost*, size_t> processes;
for (content::WebContents* contents : items) {
if (delegate->ShouldRunUnloadListenerBeforeClosing(contents))
continue;
content::RenderProcessHost* process =
contents->GetMainFrame()->GetProcess();
++processes[process];
}
// Try to fast shutdown the tabs that can close.
for (const auto& pair : processes)
pair.first->FastShutdownIfPossible(pair.second, false);
}
// We now return to our regularly scheduled shutdown procedure.
bool closed_all = true;
while (close_tracker.HasNext()) {
content::WebContents* closing_contents = close_tracker.Next();
if (!delegate->ContainsWebContents(closing_contents))
continue;
CoreTabHelper* core_tab_helper =
CoreTabHelper::FromWebContents(closing_contents);
core_tab_helper->OnCloseStarted();
// Update the explicitly closed state. If the unload handlers cancel the
// close the state is reset in Browser. We don't update the explicitly
// closed state if already marked as explicitly closed as unload handlers
// call back to this if the close is allowed.
if (!closing_contents->GetClosedByUserGesture()) {
closing_contents->SetClosedByUserGesture(
close_types & TabStripModel::CLOSE_USER_GESTURE);
}
if (delegate->RunUnloadListenerBeforeClosing(closing_contents)) {
closed_all = false;
continue;
}
delegate->OnWillDeleteWebContents(closing_contents, close_types);
delete closing_contents;
}
return closed_all;
}