CVE-2026-87545
Overview
Background
- Reader Mode
- an iOS Chrome feature that renders a simplified, distraction-free version of a web page produced by a distillation engine.
- Readability distillation
- the JavaScript path (
readability_distiller.jsrunning theReadabilitylibrary) that extracts the readable article DOM from a page and decides which elements, includingiframe/embedmedia, are preserved. - `WebStatePolicyDecider`
- the iOS web layer hook whose policy-decision callback (exercised via
GetContentRequestPolicyDecisionin the tests) can allow or cancel an individual navigation/subframe request. - Referrer leak
- disclosure of the originating page URL to a third-party server when a subframe (e.g. a tracking
iframe) is allowed to load.
Root Cause Analysis
Before the patch, the Reader Mode content policy decider allowed every non-main-frame request unconditionally: the removed test AllowsContentURLRequestForNonMainFrame asserted that “Non-main frame URLs should always be allowed” and expected ShouldAllowNavigation() for an arbitrary https://test2.url/ subframe. Because a distilled article could still contain iframe/embed elements, any third-party or tracking subframe embedded in the page would load in Reader Mode and leak the referrer, violating the invariant that a “simplified web page representation” must not fetch untrusted third-party content.
The fix introduces a shared trusted-domain allowlist (kAllowedIframeRegex / kReadabilityAllowedVideoRegex) applied in two places: readability_distiller.js now receives an allowedVideoRegex and passes it as options.allowedVideoRegex to Readability, so only matching media elements survive distillation, and the iOS policy decider now cancels subframe requests that do not match the trusted pattern (new test AllowsTrustedNonMainFrameRequestsOnly expects ShouldCancelNavigation() for the untrusted URL and ShouldAllowNavigation() only for https://www.youtube.com/embed/123). The change therefore tightens a previously unconditional allow into a regex-gated allowlist, plumbed cleanly via the new DistillerOptions/ReadabilityOptions structs.
Attack Path
- Craft a page with a third-party subframe
An attacker publishes (or gets a victim to open) a web page containing a non-trusted
iframe/embedpointing at an attacker-controlled or tracking domain such ashttps://test2.url/. - Victim invokes Reader Mode
The victim opens the page in iOS Chrome Reader Mode; the page is distilled, and pre-patch the
Readabilitycall ran with noallowedVideoRegex, so the untrusted subframe element could be retained in the distilled content. - Subframe request is allowed and referrer leaks
Pre-patch the content policy decider returned an allow decision for the non-main-frame request (
ShouldAllowNavigation()for any subframe URL), so the untrustediframeloaded and disclosed the referrer/originating context to the third-party server. - Post-patch block (contrast)
After the fix, the same untrusted subframe fails the trusted regex and its request is canceled (
ShouldCancelNavigation()), while only allowlisted media such ashttps://www.youtube.com/embed/123is permitted. (inferred) that any escalation beyond the referrer/third-party-load disclosure exists — the patch demonstrates only the information-leak surface.
Impact Assessment
(inferred) and not shown by the diff.Changed Functions
| Function | Change | Notes |
|---|---|---|
options_components/dom_distiller/core/distiller.cc |
modified | |
dom_distiller_options_components/dom_distiller/core/distiller.cc |
modified | |
destruction_allowed_components/dom_distiller/core/distiller.cc |
modified | |
DistillerImplcomponents/dom_distiller/core/distiller.h |
modified | |
ifcomponents/dom_distiller/core/javascript/readability_distiller.js |
modified |
Files Changed
components/dom_distiller/core/javascript/readability_distiller.jsios/chrome/browser/reader_mode/model/reader_mode_content_tab_helper_unittest.mmcomponents/dom_distiller/core/distiller_page.hcomponents/dom_distiller/core/distiller_options.cccomponents/dom_distiller/core/distiller_page_unittest.cccomponents/dom_distiller/core/distiller_page.ccios/chrome/browser/dom_distiller/model/BUILD.gncomponents/dom_distiller/core/distiller.hios/chrome/browser/dom_distiller/model/constants.mmios/chrome/browser/reader_mode/model/reader_mode_content_tab_helper.mmcomponents/dom_distiller/core/BUILD.gncomponents/dom_distiller/core/distiller_options.hcomponents/dom_distiller/core/readability_options.hios/chrome/browser/reader_mode/model/reader_mode_test.mmios/chrome/browser/reader_mode/model/BUILD.gnios/chrome/browser/dom_distiller/model/distiller_service_factory.mmios/chrome/browser/dom_distiller/model/constants.hcomponents/dom_distiller/core/distiller.cccomponents/dom_distiller/core/extraction_utils.cccomponents/dom_distiller/core/extraction_utils.h
Audit Directions
- Unconditional subframe/main-frame allow decisionsAudit other
WebStatePolicyDeciderimplementations and policy-decision callbacks for branches that allow non-main-frame (or any) requests without validating the destination, especially those documented as “always allowed.” - Distillation / sanitization element allowlistsReview the
Readabilityand DOM Distiller paths (and theiriframe/embed/media handling) to confirm every preserved third-party element is gated by a trusted-domain check rather than passed through by default. - Placeholder-injected script configurationCheck other grit/placeholder-substituted scripts (e.g.
$$OPTIONS,$$MIN_SCORE_PLACEHOLDER, and the new$$ALLOWED_VIDEO_REGEX) for cases where a missing or empty option silently falls back to permissive behavior instead of a safe default.
Patch
From a21bacadecca0d465fbb2f28fabf1590439c06c0 Mon Sep 17 00:00:00 2001
From: Quentin Pubert <qpubert@google.com>
Date: Fri, 31 Jul 2026 05:45:07 -0700
Subject: [PATCH] [iOS][ReaderMode] Block non-trusted iframe requests in Reader Mode
Since Reader Mode is a simplified web page representation, it needs to
prevent tracking or other third-party iframes from loading and leaking
referrers.
This CL introduces a strict iframe domain allowlist matching the trusted
domains allowed by the Readability.js media/video engine.
- Declares a shared regex in C++ constants (kAllowedIframeRegex) in
ios/chrome/browser/dom_distiller/model/constants.h.
- Introduces DistillerOptions and ReadabilityOptions to cleanly pass
distillation configuration parameters down to Readability.js and
DOM Distiller.
- Injects this regex dynamically as a placeholder value
($$ALLOWED_VIDEO_REGEX) inside readability_distiller.js.
- Uses this identical regex in the iOS ReaderModeContentTabHelper policy
decider to block all subframe requests not matching the trusted
pattern (e.g. YouTube).
Bug: 523243507
Change-Id: I86cc4dbe5ae91a7d7bcf785cf8e1ca5e6a9cb717
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8019571
Reviewed-by: Brandon Wylie <wylieb@google.com>
Reviewed-by: Olivier Robin <olivierrobin@chromium.org>
Auto-Submit: Quentin Pubert <qpubert@google.com>
Commit-Queue: Quentin Pubert <qpubert@google.com>
Cr-Commit-Position: refs/heads/main@{#1671776}
---
diff --git a/components/dom_distiller/core/distiller.cc b/components/dom_distiller/core/distiller.cc
index 4d7f73f8..a80fe3b 100644
--- a/components/dom_distiller/core/distiller.cc
+++ b/components/dom_distiller/core/distiller.cc
@@ -34,16 +34,22 @@
DistillerFactoryImpl::DistillerFactoryImpl(
std::unique_ptr<DistillerURLFetcherFactory> distiller_url_fetcher_factory,
+ const DistillerOptions& options)
+ : distiller_url_fetcher_factory_(std::move(distiller_url_fetcher_factory)),
+ options_(options) {}
+
+DistillerFactoryImpl::DistillerFactoryImpl(
+ std::unique_ptr<DistillerURLFetcherFactory> distiller_url_fetcher_factory,
const dom_distiller::proto::DomDistillerOptions& dom_distiller_options)
: distiller_url_fetcher_factory_(std::move(distiller_url_fetcher_factory)),
- dom_distiller_options_(dom_distiller_options) {}
+ options_(dom_distiller_options) {}
DistillerFactoryImpl::~DistillerFactoryImpl() = default;
std::unique_ptr<Distiller> DistillerFactoryImpl::CreateDistiller() {
// This default implementation has the same behavior for all URLs.
- std::unique_ptr<DistillerImpl> distiller(new DistillerImpl(
- *distiller_url_fetcher_factory_, dom_distiller_options_));
+ std::unique_ptr<DistillerImpl> distiller(
+ new DistillerImpl(*distiller_url_fetcher_factory_, options_));
return std::move(distiller);
}
@@ -53,12 +59,18 @@
DistillerImpl::DistillerImpl(
const DistillerURLFetcherFactory& distiller_url_fetcher_factory,
- const dom_distiller::proto::DomDistillerOptions& dom_distiller_options)
+ const DistillerOptions& options)
: distiller_url_fetcher_factory_(distiller_url_fetcher_factory),
- dom_distiller_options_(dom_distiller_options),
+ options_(options),
max_pages_in_article_(kMaxPagesInArticle),
destruction_allowed_(true) {}
+DistillerImpl::DistillerImpl(
+ const DistillerURLFetcherFactory& distiller_url_fetcher_factory,
+ const dom_distiller::proto::DomDistillerOptions& dom_distiller_options)
+ : DistillerImpl(distiller_url_fetcher_factory,
+ DistillerOptions(dom_distiller_options)) {}
+
DistillerImpl::~DistillerImpl() {
DCHECK(destruction_allowed_);
}
@@ -128,7 +140,7 @@
// TODO(gilmanmh): Investigate whether this needs to be
// base::BindRepeating() or if base::BindOnce() can be used instead.
distiller_page_->DistillPage(
- url, dom_distiller_options_,
+ url, options_,
base::BindRepeating(&DistillerImpl::OnPageDistillationFinished,
weak_factory_.GetWeakPtr(), page_num, url));
}
diff --git a/components/dom_distiller/core/distiller.h b/components/dom_distiller/core/distiller.h
index b5051c9..330b398 100644
--- a/components/dom_distiller/core/distiller.h
+++ b/components/dom_distiller/core/distiller.h
@@ -18,9 +18,11 @@
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "components/dom_distiller/core/article_distillation_update.h"
+#include "components/dom_distiller/core/distiller_options.h"
#include "components/dom_distiller/core/distiller_page.h"
#include "components/dom_distiller/core/distiller_url_fetcher.h"
#include "components/dom_distiller/core/proto/distilled_article.pb.h"
+#include "components/dom_distiller/core/readability_options.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
#include "url/gurl.h"
@@ -62,18 +64,23 @@
public:
DistillerFactoryImpl(
std::unique_ptr<DistillerURLFetcherFactory> distiller_url_fetcher_factory,
+ const DistillerOptions& options);
+ DistillerFactoryImpl(
+ std::unique_ptr<DistillerURLFetcherFactory> distiller_url_fetcher_factory,
const dom_distiller::proto::DomDistillerOptions& dom_distiller_options);
~DistillerFactoryImpl() override;
std::unique_ptr<Distiller> CreateDistiller() override;
private:
std::unique_ptr<DistillerURLFetcherFactory> distiller_url_fetcher_factory_;
- dom_distiller::proto::DomDistillerOptions dom_distiller_options_;
+ DistillerOptions options_;
};
// Distills a article from a page and associated pages.
class DistillerImpl : public Distiller {
public:
+ DistillerImpl(const DistillerURLFetcherFactory& distiller_url_fetcher_factory,
+ const DistillerOptions& options);
DistillerImpl(
const DistillerURLFetcherFactory& distiller_url_fetcher_factory,
const dom_distiller::proto::DomDistillerOptions& dom_distiller_options);
@@ -161,7 +168,7 @@
distiller_url_fetcher_factory_;
std::unique_ptr<DistillerPage> distiller_page_;
- dom_distiller::proto::DomDistillerOptions dom_distiller_options_;
+ DistillerOptions options_;
DistillationFinishedCallback finished_cb_;
DistillationUpdateCallback update_cb_;
diff --git a/components/dom_distiller/core/distiller_options.cc b/components/dom_distiller/core/distiller_options.cc
new file mode 100644
index 0000000..6b45b611
--- /dev/null
+++ b/components/dom_distiller/core/distiller_options.cc
@@ -0,0 +1,20 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/dom_distiller/core/distiller_options.h"
+
+namespace dom_distiller {
+
+DistillerOptions::DistillerOptions() = default;
+
+DistillerOptions::DistillerOptions(
+ proto::DomDistillerOptions dom_distiller_options)
+ : dom_distiller(std::move(dom_distiller_options)) {}
+
+DistillerOptions::DistillerOptions(ReadabilityOptions readability_options)
+ : readability(std::move(readability_options)) {}
+
+DistillerOptions::~DistillerOptions() = default;
+
+} // namespace dom_distiller
diff --git a/components/dom_distiller/core/distiller_options.h b/components/dom_distiller/core/distiller_options.h
new file mode 100644
index 0000000..781f754
--- /dev/null
+++ b/components/dom_distiller/core/distiller_options.h
@@ -0,0 +1,29 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOM_DISTILLER_CORE_DISTILLER_OPTIONS_H_
+#define COMPONENTS_DOM_DISTILLER_CORE_DISTILLER_OPTIONS_H_
+
+#include "components/dom_distiller/core/readability_options.h"
+#include "third_party/dom_distiller_js/dom_distiller.pb.h"
+
+namespace dom_distiller {
+
+// Configuration options for page distillation, grouping options for both the
+// DOM Distiller and Readability engines.
+struct DistillerOptions {
+ DistillerOptions();
+ explicit DistillerOptions(proto::DomDistillerOptions dom_distiller_options);
+ explicit DistillerOptions(ReadabilityOptions readability_options);
+ ~DistillerOptions();
+
+ // Options specific to the DOM Distiller engine.
+ proto::DomDistillerOptions dom_distiller;
+ // Options specific to the Readability engine.
+ ReadabilityOptions readability;
+};
+
+} // namespace dom_distiller
+
+#endif // COMPONENTS_DOM_DISTILLER_CORE_DISTILLER_OPTIONS_H_
diff --git a/components/dom_distiller/core/distiller_page.cc b/components/dom_distiller/core/distiller_page.cc
index 59675f5..5e08c44 100644
--- a/components/dom_distiller/core/distiller_page.cc
+++ b/components/dom_distiller/core/distiller_page.cc
@@ -49,10 +49,9 @@
DistillerPage::~DistillerPage() = default;
-void DistillerPage::DistillPage(
- const GURL& gurl,
- const dom_distiller::proto::DomDistillerOptions options,
- DistillerPageCallback callback) {
+void DistillerPage::DistillPage(const GURL& gurl,
+ const DistillerOptions& options,
+ DistillerPageCallback callback) {
CHECK(ready_);
CHECK(callback);
CHECK(!distiller_page_callback_);
@@ -64,16 +63,24 @@
std::string script;
switch (GetDistillerType()) {
case DistillerType::kReadability:
- script = GetReadabilityDistillerScript();
+ script = GetReadabilityDistillerScript(options.readability);
break;
case DistillerType::kDOMDistiller:
- script = GetDistillerScriptWithOptions(options);
+ script = GetDistillerScriptWithOptions(options.dom_distiller);
break;
}
DistillPageImpl(gurl, script);
}
+void DistillerPage::DistillPage(
+ const GURL& gurl,
+ const dom_distiller::proto::DomDistillerOptions dom_distiller_options,
+ DistillerPageCallback callback) {
+ DistillPage(gurl, DistillerOptions(dom_distiller_options),
+ std::move(callback));
+}
+
void DistillerPage::OnDistillationDone(const GURL& page_url,
const base::Value* value) {
DCHECK(!ready_);
diff --git a/components/dom_distiller/core/distiller_page.h b/components/dom_distiller/core/distiller_page.h
index c47fa8e..8a08457 100644
--- a/components/dom_distiller/core/distiller_page.h
+++ b/components/dom_distiller/core/distiller_page.h
@@ -10,6 +10,7 @@
#include "base/functional/callback.h"
#include "base/values.h"
+#include "components/dom_distiller/core/distiller_options.h"
#include "components/dom_distiller/core/dom_distiller_constants.h"
#include "third_party/dom_distiller_js/dom_distiller.pb.h"
#include "ui/gfx/geometry/size.h"
@@ -54,6 +55,9 @@
// for a given |url| and |options|, any DistillerPage implementation will
// extract the same content.
void DistillPage(const GURL& url,
+ const DistillerOptions& options,
+ DistillerPageCallback callback);
+ void DistillPage(const GURL& url,
const proto::DomDistillerOptions options,
DistillerPageCallback callback);
diff --git a/components/dom_distiller/core/extraction_utils.cc b/components/dom_distiller/core/extraction_utils.cc
index 6a0af53..a5d32cf 100644
--- a/components/dom_distiller/core/extraction_utils.cc
+++ b/components/dom_distiller/core/extraction_utils.cc
@@ -14,6 +14,7 @@
#include "base/strings/string_util.h"
#include "base/values.h"
#include "components/dom_distiller/core/dom_distiller_features.h"
+#include "components/dom_distiller/core/readability_options.h"
#include "components/grit/components_resources.h"
#include "third_party/dom_distiller_js/dom_distiller.pb.h"
#include "third_party/dom_distiller_js/dom_distiller_json_converter.h"
@@ -24,6 +25,7 @@
const char* kOptionsPlaceholder = "$$OPTIONS";
const char* kMinScorePlaceholder = "$$MIN_SCORE_PLACEHOLDER";
const char* kMinContentLengthPlaceholder = "$$MIN_CONTENT_LENGTH_PLACEHOLDER";
+const char* kAllowedVideoRegexPlaceholder = "$$ALLOWED_VIDEO_REGEX";
void ReplaceScriptPlaceholder(std::string& script,
const char* placeholder,
@@ -52,11 +54,23 @@
return script;
}
-std::string GetReadabilityDistillerScript() {
+std::string GetReadabilityDistillerScript(const ReadabilityOptions& options) {
std::string script =
ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
IDR_READABILITY_DISTILLER_JS);
CHECK(!script.empty());
+
+ std::string allowed_regex_json;
+ if (options.allowed_video_regex.has_value()) {
+ if (!base::JSONWriter::Write(base::Value(*options.allowed_video_regex),
+ &allowed_regex_json)) {
+ NOTREACHED();
+ }
+ } else {
+ allowed_regex_json = "undefined";
+ }
+ ReplaceScriptPlaceholder(script, kAllowedVideoRegexPlaceholder,
+ allowed_regex_json);
return script;
}
diff --git a/components/dom_distiller/core/extraction_utils.h b/components/dom_distiller/core/extraction_utils.h
index 2a503f4..7a3f886 100644
--- a/components/dom_distiller/core/extraction_utils.h
+++ b/components/dom_distiller/core/extraction_utils.h
@@ -7,6 +7,7 @@
#include <string>
+#include "components/dom_distiller/core/readability_options.h"
#include "third_party/dom_distiller_js/dom_distiller.pb.h"
namespace base {
@@ -29,8 +30,9 @@
std::string GetDistillerScriptWithOptions(
const dom_distiller::proto::DomDistillerOptions& options);
-// Returns the Readability JavaScript web page distillation script.
-std::string GetReadabilityDistillerScript();
+// Returns the Readability JavaScript web page distillation script with the
+// given options.
+std::string GetReadabilityDistillerScript(const ReadabilityOptions& options);
// Returns the Javascript heuristic to determine if web pages are suitable for
// reader mode.
diff --git a/components/dom_distiller/core/javascript/readability_distiller.js b/components/dom_distiller/core/javascript/readability_distiller.js
index 450a932..be7c8d2e 100644
--- a/components/dom_distiller/core/javascript/readability_distiller.js
+++ b/components/dom_distiller/core/javascript/readability_distiller.js
@@ -3,7 +3,7 @@
// found in the LICENSE file.
// Runs readability heuristic on the page and return the result.
-(function() {
+(function(allowedVideoRegex) {
try {
function initialize() {
// This include will be processed at build time by grit.
@@ -14,7 +14,12 @@
}
initialize();
- const article = new Readability(document.cloneNode(/*deep=*/ true)).parse();
+ const options = {};
+ if (allowedVideoRegex) {
+ options.allowedVideoRegex = new RegExp('//' + allowedVideoRegex, 'i');
+ }
+ const clonedDoc = document.cloneNode(/*deep=*/ true);
+ const article = new Readability(clonedDoc, options).parse();
return article;
} catch (e) {
window.console.error('Error during distillation: ' + e);
@@ -23,4 +28,4 @@
}
}
return undefined;
-})();
+})($$ALLOWED_VIDEO_REGEX);
diff --git a/components/dom_distiller/core/readability_options.h b/components/dom_distiller/core/readability_options.h
new file mode 100644
index 0000000..e4dc99c
--- /dev/null
+++ b/components/dom_distiller/core/readability_options.h
@@ -0,0 +1,25 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DOM_DISTILLER_CORE_READABILITY_OPTIONS_H_
+#define COMPONENTS_DOM_DISTILLER_CORE_READABILITY_OPTIONS_H_
+
+#include <optional>
+#include <string>
+
+namespace dom_distiller {
+
+// Configuration options specifically for the Readability engine.
+struct ReadabilityOptions {
+ // A regular expression matching allowed video elements that can be preserved.
+ // Note: Readability.js tests this regular expression against all attributes
+ // of iframe and embed elements (such as `src` or `data-src`) as well as their
+ // `innerHTML`.
+ // If std::nullopt or empty, the default Readability regex is used.
+ std::optional<std::string> allowed_video_regex;
+};
+
+} // namespace dom_distiller
... (file diff truncated)
Regression Test / PoC
diff --git a/components/dom_distiller/core/distiller_page_unittest.cc b/components/dom_distiller/core/distiller_page_unittest.cc
index e7bcea0f..a7c891eb 100644
--- a/components/dom_distiller/core/distiller_page_unittest.cc
+++ b/components/dom_distiller/core/distiller_page_unittest.cc
@@ -15,6 +15,7 @@
#include "components/dom_distiller/core/dom_distiller_constants.h"
#include "components/dom_distiller/core/dom_distiller_features.h"
#include "components/dom_distiller/core/extraction_utils.h"
+#include "components/dom_distiller/core/readability_options.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/dom_distiller_js/dom_distiller.pb.h"
#include "url/gurl.h"
@@ -308,6 +309,17 @@
}
#endif
+// Test that the readability script options are injected correctly.
+TEST_F(DistillerPageTest, ReadabilityScriptOptionsHandling) {
+ ReadabilityOptions custom_options;
+ std::string default_script = GetReadabilityDistillerScript(custom_options);
+ EXPECT_NE(std::string::npos, default_script.find("})(undefined);"));
+
+ custom_options.allowed_video_regex = "youtube|vimeo";
+ std::string custom_script = GetReadabilityDistillerScript(custom_options);
+ EXPECT_NE(std::string::npos, custom_script.find("})(\"youtube|vimeo\");"));
+}
+
} // namespace
} // namespace dom_distiller
diff --git a/ios/chrome/browser/reader_mode/model/reader_mode_content_tab_helper_unittest.mm b/ios/chrome/browser/reader_mode/model/reader_mode_content_tab_helper_unittest.mm
index 435d322..ab94297 100644
--- a/ios/chrome/browser/reader_mode/model/reader_mode_content_tab_helper_unittest.mm
+++ b/ios/chrome/browser/reader_mode/model/reader_mode_content_tab_helper_unittest.mm
@@ -150,22 +150,29 @@
EXPECT_NSEQ(non_content_request, delegate_->last_canceled_request());
}
-// Tests that non-main frame URL requests are always allowed. This is a
-// regression test for crbug.com/426443192.
-TEST_F(ReaderModeContentTabHelperTest, AllowsContentURLRequestForNonMainFrame) {
- NSURL* non_content_url = [NSURL URLWithString:@"https://test2.url/"];
- NSURLRequest* non_content_request =
- [NSURLRequest requestWithURL:non_content_url];
+// Tests that non-main frame URL requests are allowed if they are in the trusted
+// allowlist, and canceled otherwise.
+TEST_F(ReaderModeContentTabHelperTest, AllowsTrustedNonMainFrameRequestsOnly) {
+ NSURL* trusted_url =
+ [NSURL URLWithString:@"https://www.youtube.com/embed/123"];
+ NSURLRequest* trusted_request = [NSURLRequest requestWithURL:trusted_url];
web::WebStatePolicyDecider::RequestInfo non_main_frame_request_info(
ui::PAGE_TRANSITION_FIRST, /*target_frame_is_main=*/false, false, false,
false, false);
- // Non-main frame URLs should always be allowed.
std::optional<web::WebStatePolicyDecider::PolicyDecision> policy_decision =
- GetContentRequestPolicyDecision(non_content_request,
+ GetContentRequestPolicyDecision(trusted_request,
non_main_frame_request_info);
EXPECT_TRUE(policy_decision);
EXPECT_TRUE(policy_decision->ShouldAllowNavigation());
+
+ NSURL* untrusted_url = [NSURL URLWithString:@"https://test2.url/"];
+ NSURLRequest* untrusted_request = [NSURLRequest requestWithURL:untrusted_url];
+
+ policy_decision = GetContentRequestPolicyDecision(
+ untrusted_request, non_main_frame_request_info);
+ EXPECT_TRUE(policy_decision);
+ EXPECT_TRUE(policy_decision->ShouldCancelNavigation());
}
// Tests that the delegate is notified only when the loaded page URL matches the
diff --git a/ios/chrome/browser/reader_mode/model/reader_mode_test.mm b/ios/chrome/browser/reader_mode/model/reader_mode_test.mm
index c5a9a877..d0d965e 100644
--- a/ios/chrome/browser/reader_mode/model/reader_mode_test.mm
+++ b/ios/chrome/browser/reader_mode/model/reader_mode_test.mm
@@ -16,6 +16,7 @@
#import "components/optimization_guide/proto/hints.pb.h"
#import "components/sync/test/test_sync_service.h"
#import "components/translate/core/browser/translate_pref_names.h"
+#import "ios/chrome/browser/dom_distiller/model/constants.h"
#import "ios/chrome/browser/dom_distiller/model/distiller_service_factory.h"
#import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
@@ -24,6 +25,7 @@
#import "ios/chrome/browser/optimization_guide/model/optimization_guide_service.h"
#import "ios/chrome/browser/optimization_guide/model/optimization_guide_service_factory.h"
#import "ios/chrome/browser/overlays/model/public/overlay_request_queue.h"
+#import "ios/chrome/browser/reader_mode/model/constants.h"
#import "ios/chrome/browser/reader_mode/model/features.h"
#import "ios/chrome/browser/reader_mode/model/reader_mode_java_script_feature.h"
#import "ios/chrome/browser/reader_mode/model/reader_mode_scroll_anchor_java_script_feature.h"
@@ -164,8 +166,10 @@
// Set up the fake web frame to return a custom result after executing
// the Readability Javascript.
+ dom_distiller::ReadabilityOptions options;
+ options.allowed_video_regex = kReadabilityAllowedVideoRegex;
std::u16string readability_script =
- base::UTF8ToUTF16(dom_distiller::GetReadabilityDistillerScript());
+ base::UTF8ToUTF16(dom_distiller::GetReadabilityDistillerScript(options));
base::DictValue readability_result;
readability_result.Set("content", distilled_content);
readability_result.Set("title", "fake title");