blob: 2d49d284bac97a01e651431ad6a272480cfbb32e [file] [log] [blame]
// Copyright 2019 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/web_applications/web_app_url_loader.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/check.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/timer/timer.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/url_constants.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
namespace web_app {
namespace {
using UrlComparison = WebAppUrlLoader::UrlComparison;
bool EqualsWithComparison(const GURL& a,
const GURL& b,
UrlComparison url_comparison) {
DCHECK(a.is_valid());
DCHECK(b.is_valid());
if (a == b)
return true;
GURL::Replacements replace;
switch (url_comparison) {
case UrlComparison::kExact:
return false;
case UrlComparison::kSameOrigin:
replace.ClearPath();
[[fallthrough]];
case UrlComparison::kIgnoreQueryParamsAndRef:
replace.ClearQuery();
replace.ClearRef();
break;
}
return a.ReplaceComponents(replace) == b.ReplaceComponents(replace);
}
class LoaderTask : public content::WebContentsObserver {
public:
LoaderTask() = default;
LoaderTask(const LoaderTask&) = delete;
LoaderTask& operator=(const LoaderTask&) = delete;
LoaderTask(LoaderTask&&) = delete;
LoaderTask& operator=(LoaderTask&&) = delete;
~LoaderTask() override = default;
void LoadUrl(const GURL& url,
content::WebContents* web_contents,
UrlComparison url_comparison,
WebAppUrlLoader::ResultCallback callback) {
url_ = url;
url_comparison_ = url_comparison;
callback_ = std::move(callback);
Observe(web_contents);
content::NavigationController::LoadURLParams load_params(url);
load_params.transition_type = ui::PAGE_TRANSITION_GENERATED;
web_contents->GetController().LoadURLWithParams(load_params);
timer_.Start(FROM_HERE, WebAppUrlLoader::kSecondsToWaitForWebContentsLoad,
base::BindOnce(&LoaderTask::OnLoadUrlTimeout,
// OneShotTimer is owned by this class and
// it guarantees that it will never run after
// it's destroyed.
base::Unretained(this)));
}
// WebContentsObserver
// DidFinishLoad doesn't always get called after the page has fully loaded.
// TODO(ortuno): Use DidStopLoading instead.
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override {
if (IsSubframeLoad(render_frame_host)) {
return;
}
// Flush all DidFinishLoad events until about:blank loaded.
if ((url_.IsAboutBlank() && !validated_url.IsAboutBlank()) ||
(!url_.IsAboutBlank() && validated_url.IsAboutBlank())) {
return;
}
timer_.Stop();
if (validated_url == content::kUnreachableWebDataURL) {
// Navigation ends up in an error page. For example, network errors and
// policy blocked URLs.
// TODO(https://crbug.com/1071300): Handle error codes appropriately.
PostResultTask(WebAppUrlLoader::Result::kFailedErrorPageLoaded);
return;
}
if (EqualsWithComparison(validated_url, url_, url_comparison_)) {
PostResultTask(WebAppUrlLoader::Result::kUrlLoaded);
return;
}
LOG(ERROR) << "Error loading " << url_ << " page redirected to "
<< validated_url;
PostResultTask(WebAppUrlLoader::Result::kRedirectedUrlLoaded);
}
bool IsSubframeLoad(content::RenderFrameHost* render_frame_host) const {
return web_contents()->GetPrimaryMainFrame() != render_frame_host;
}
void DidFailLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code) override {
if (IsSubframeLoad(render_frame_host)) {
return;
}
// Flush all DidFailLoad events until about:blank loaded.
if (url_.IsAboutBlank())
return;
timer_.Stop();
LOG(ERROR) << "Error loading " << url_ << " page failed to load.";
PostResultTask(WebAppUrlLoader::Result::kFailedUnknownReason);
}
void WebContentsDestroyed() override {
timer_.Stop();
PostResultTask(WebAppUrlLoader::Result::kFailedWebContentsDestroyed);
}
private:
void OnLoadUrlTimeout() {
web_contents()->Stop();
LOG(ERROR) << "Error loading " << url_ << " page took too long to load.";
PostResultTask(WebAppUrlLoader::Result::kFailedPageTookTooLong);
}
void PostResultTask(WebAppUrlLoader::Result result) {
Observe(nullptr);
// Post a task to avoid reentrancy issues e.g. adding a WebContentsObserver
// while a previous observer call is being executed.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), result));
}
GURL url_;
UrlComparison url_comparison_;
WebAppUrlLoader::ResultCallback callback_;
base::OneShotTimer timer_;
base::WeakPtrFactory<LoaderTask> weak_ptr_factory_{this};
};
} // namespace
WebAppUrlLoader::WebAppUrlLoader() = default;
WebAppUrlLoader::~WebAppUrlLoader() = default;
void WebAppUrlLoader::LoadUrl(const GURL& url,
content::WebContents* web_contents,
UrlComparison url_comparison,
ResultCallback callback) {
auto loader_task = std::make_unique<LoaderTask>();
auto* loader_task_ptr = loader_task.get();
loader_task_ptr->LoadUrl(
url, web_contents, url_comparison,
base::BindOnce(
[](ResultCallback callback, std::unique_ptr<LoaderTask> task,
Result result) {
std::move(callback).Run(result);
task.reset();
},
std::move(callback), std::move(loader_task)));
}
void WebAppUrlLoader::PrepareForLoad(content::WebContents* web_contents,
ResultCallback callback) {
LoadUrl(GURL(url::kAboutBlankURL), web_contents, UrlComparison::kExact,
base::BindOnce(
[](ResultCallback callback, Result result) {
base::UmaHistogramEnumeration(
"Webapp.WebAppUrlLoaderPrepareForLoadResult", result);
std::move(callback).Run(result);
},
std::move(callback)));
}
const char* ConvertUrlLoaderResultToString(WebAppUrlLoader::Result result) {
using Result = WebAppUrlLoader::Result;
switch (result) {
case Result::kUrlLoaded:
return "UrlLoaded";
case Result::kRedirectedUrlLoaded:
return "RedirectedUrlLoaded";
case Result::kFailedUnknownReason:
return "FailedUnknownReason";
case Result::kFailedPageTookTooLong:
return "FailedPageTookTooLong";
case Result::kFailedWebContentsDestroyed:
return "FailedWebContentsDestroyed";
case Result::kFailedErrorPageLoaded:
return "FailedErrorPageLoaded";
}
}
} // namespace web_app