#include "components/dom_distiller/content/browser/dom_distiller_viewer_source.h"
#include <deque>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/back_forward_cache/back_forward_cache_disable.h"
#include "components/dom_distiller/content/browser/distiller_javascript_utils.h"
#include "components/dom_distiller/core/distilled_page_prefs.h"
#include "components/dom_distiller/core/dom_distiller_request_view_base.h"
#include "components/dom_distiller/core/dom_distiller_service.h"
#include "components/dom_distiller/core/experiments.h"
#include "components/dom_distiller/core/task_tracker.h"
#include "components/dom_distiller/core/url_constants.h"
#include "components/dom_distiller/core/url_utils.h"
#include "components/dom_distiller/core/viewer.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/url_util.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/rect.h"
namespace dom_distiller {
class DomDistillerViewerSource::RequestViewerHandle
: public DomDistillerRequestViewBase,
public content::WebContentsObserver {
public:
RequestViewerHandle(content::WebContents* web_contents,
const GURL& expected_url,
DistilledPagePrefs* distilled_page_prefs);
~RequestViewerHandle() override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
void PrimaryMainFrameRenderProcessGone(
base::TerminationStatus status) override;
void WebContentsDestroyed() override;
void DOMContentLoaded(content::RenderFrameHost* render_frame_host) override;
private:
void SendJavaScript(const std::string& buffer) override;
void Cancel();
const GURL expected_url_;
bool waiting_for_page_ready_;
std::deque<std::string> buffers_;
};
DomDistillerViewerSource::RequestViewerHandle::RequestViewerHandle(
content::WebContents* web_contents,
const GURL& expected_url,
DistilledPagePrefs* distilled_page_prefs)
: DomDistillerRequestViewBase(distilled_page_prefs),
expected_url_(expected_url),
waiting_for_page_ready_(true) {
content::WebContentsObserver::Observe(web_contents);
distilled_page_prefs_->AddObserver(this);
}
DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() {
distilled_page_prefs_->RemoveObserver(this);
}
void DomDistillerViewerSource::RequestViewerHandle::SendJavaScript(
const std::string& buffer) {
if (waiting_for_page_ready_) {
buffers_.push_back(buffer);
} else {
DCHECK(buffers_.empty());
if (web_contents()) {
RunIsolatedJavaScript(web_contents()->GetPrimaryMainFrame(), buffer);
}
}
}
void DomDistillerViewerSource::RequestViewerHandle::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsInPrimaryMainFrame() ||
!navigation_handle->HasCommitted())
return;
const GURL& navigation = navigation_handle->GetURL();
bool expected_main_view_request = navigation == expected_url_;
if (navigation_handle->IsSameDocument() || expected_main_view_request) {
return;
}
content::BackForwardCache::DisableForRenderFrameHost(
navigation_handle->GetPreviousRenderFrameHostId(),
back_forward_cache::DisabledReason(
back_forward_cache::DisabledReasonId::kDomDistillerViewerSource));
Cancel();
}
void DomDistillerViewerSource::RequestViewerHandle::
PrimaryMainFrameRenderProcessGone(base::TerminationStatus status) {
Cancel();
}
void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
Cancel();
}
void DomDistillerViewerSource::RequestViewerHandle::Cancel() {
content::WebContentsObserver::Observe(nullptr);
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
this);
}
void DomDistillerViewerSource::RequestViewerHandle::DOMContentLoaded(
content::RenderFrameHost* render_frame_host) {
if (render_frame_host->GetParentOrOuterDocument()) {
return;
}
#if BUILDFLAG(IS_ANDROID)
content::HostZoomMap* host_zoom_map =
content::HostZoomMap::GetForWebContents(web_contents());
host_zoom_map->SetTemporaryZoomLevel(
web_contents()->GetPrimaryMainFrame()->GetGlobalId(), 0.0);
#endif
while (!buffers_.empty()) {
RunIsolatedJavaScript(web_contents()->GetPrimaryMainFrame(),
buffers_.front());
buffers_.pop_front();
}
waiting_for_page_ready_ = false;
}
DomDistillerViewerSource::DomDistillerViewerSource(
DomDistillerServiceInterface* dom_distiller_service)
: scheme_(kDomDistillerScheme),
dom_distiller_service_(dom_distiller_service) {}
DomDistillerViewerSource::~DomDistillerViewerSource() = default;
std::string DomDistillerViewerSource::GetSource() {
return scheme_ + "://";
}
void DomDistillerViewerSource::StartDataRequest(
const GURL& url,
const content::WebContents::Getter& wc_getter,
content::URLDataSource::GotDataCallback callback) {
const std::string path = URLDataSource::URLToRequestPath(url);
content::WebContents* web_contents = wc_getter.Run();
if (!web_contents)
return;
#if !BUILDFLAG(IS_ANDROID)
blink::web_pref::WebPreferences prefs =
web_contents->GetOrCreateWebPreferences();
prefs.strict_mixed_content_checking = true;
web_contents->SetWebPreferences(prefs);
#endif
if (kViewerCssPath == path) {
std::string css = viewer::GetCss();
std::move(callback).Run(
base::MakeRefCounted<base::RefCountedString>(std::move(css)));
return;
}
if (kViewerLoadingImagePath == path) {
std::string image = viewer::GetLoadingImage();
std::move(callback).Run(
base::MakeRefCounted<base::RefCountedString>(std::move(image)));
return;
}
auto remainder = base::RemovePrefix(path, kViewerSaveFontScalingPath);
if (remainder) {
double scale = 1.0;
if (base::StringToDouble(*remainder, &scale)) {
dom_distiller_service_->GetDistilledPagePrefs()->SetUserPrefFontScaling(
scale);
}
}
const std::string query = GURL("https://host/" + path).GetQuery();
GURL request_url = web_contents->GetVisibleURL();
if (request_url.GetQuery() != query || request_url.GetPath() != "/") {
request_url = GURL();
}
RequestViewerHandle* request_viewer_handle =
new RequestViewerHandle(web_contents, request_url,
dom_distiller_service_->GetDistilledPagePrefs());
std::unique_ptr<ViewerHandle> viewer_handle = viewer::CreateViewRequest(
dom_distiller_service_, request_url, request_viewer_handle,
web_contents->GetContainerBounds().size());
GURL current_url(url_utils::GetOriginalUrlFromDistillerUrl(request_url));
std::string unsafe_page_html = viewer::GetArticleTemplateHtml(
dom_distiller_service_->GetDistilledPagePrefs()->GetTheme(),
dom_distiller_service_->GetDistilledPagePrefs()->GetFontFamily(),
std::string(), false);
if (viewer_handle) {
request_viewer_handle->TakeViewerHandle(std::move(viewer_handle));
} else {
request_viewer_handle->FlagAsErrorPage();
}
std::move(callback).Run(base::MakeRefCounted<base::RefCountedString>(
std::move(unsafe_page_html)));
}
std::string DomDistillerViewerSource::GetMimeType(const GURL& url) {
const std::string_view path = url.path().substr(1);
if (kViewerCssPath == path)
return "text/css";
if (kViewerLoadingImagePath == path)
return "image/svg+xml";
return "text/html";
}
bool DomDistillerViewerSource::ShouldServiceRequest(
const GURL& url,
content::BrowserContext* browser_context,
int render_process_id) {
return url.SchemeIs(scheme_);
}
std::string DomDistillerViewerSource::GetContentSecurityPolicy(
network::mojom::CSPDirectiveName directive) {
if (directive == network::mojom::CSPDirectiveName::StyleSrc) {
return "style-src 'self' https://fonts.googleapis.com;";
} else if (directive == network::mojom::CSPDirectiveName::ChildSrc) {
return "child-src *;";
} else if (directive ==
network::mojom::CSPDirectiveName::RequireTrustedTypesFor ||
directive == network::mojom::CSPDirectiveName::TrustedTypes) {
return std::string();
}
return content::URLDataSource::GetContentSecurityPolicy(directive);
}
}