Commit f4bdfb51 authored by Finnur Thorarinsson's avatar Finnur Thorarinsson Committed by Commit Bot

RSS Extension: Build script, closure, hardening.

- Sandbox the iframe and sanitize the input used.
- Remove special 'synchronous' testing flag (not needed).
  - Multi-line quote requirement also no longer needed.
- Change default reader list
  - (replace deprecated Bloglines service with Newsblur).
- Deflake and re-enable the RSS extension tests.
- Up the version by two (from 2.2.3 to 2.2.5, since 2.2.4
  is live already).

Bug: 340354, b/137075999
Change-Id: I6eac37fae9e2228aeabe68ccf1fd34b9b36ac6ba
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2264059
Commit-Queue: Finnur Thorarinsson <finnur@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#785861}
parent 56da23ea
...@@ -74,7 +74,7 @@ GURL GetFeedUrl(net::EmbeddedTestServer* server, ...@@ -74,7 +74,7 @@ GURL GetFeedUrl(net::EmbeddedTestServer* server,
// sniffing won't work, in other words, as is the case for malformed feeds. // sniffing won't work, in other words, as is the case for malformed feeds.
return GURL(std::string(kExtensionScheme) + url::kStandardSchemeSeparator + return GURL(std::string(kExtensionScheme) + url::kStandardSchemeSeparator +
extension_id + std::string(kSubscribePage) + std::string("?") + extension_id + std::string(kSubscribePage) + std::string("?") +
feed_url.spec() + std::string("&synchronous")); feed_url.spec());
} else { } else {
// Navigate to the feed content (which will cause the extension to try to // Navigate to the feed content (which will cause the extension to try to
// sniff the type and display the subscribe page in another tab. // sniff the type and display the subscribe page in another tab.
...@@ -82,6 +82,37 @@ GURL GetFeedUrl(net::EmbeddedTestServer* server, ...@@ -82,6 +82,37 @@ GURL GetFeedUrl(net::EmbeddedTestServer* server,
} }
} }
class NamedFrameCreatedObserver : public content::WebContentsObserver {
public:
NamedFrameCreatedObserver(WebContents* web_contents,
const std::string& frame_name)
: WebContentsObserver(web_contents), frame_name_(frame_name) {}
content::RenderFrameHost* Wait() {
if (!frame_) {
run_loop_.Run();
}
return frame_;
}
private:
void RenderFrameCreated(
content::RenderFrameHost* render_frame_host) override {
if (render_frame_host->GetFrameName() != frame_name_)
return;
frame_ = render_frame_host;
run_loop_.Quit();
}
base::RunLoop run_loop_;
content::RenderFrameHost* frame_ = nullptr;
std::string frame_name_;
DISALLOW_COPY_AND_ASSIGN(NamedFrameCreatedObserver);
};
bool ValidatePageElement(content::RenderFrameHost* frame, bool ValidatePageElement(content::RenderFrameHost* frame,
const std::string& javascript, const std::string& javascript,
const std::string& expected_value) { const std::string& expected_value) {
...@@ -108,18 +139,29 @@ void NavigateToFeedAndValidate(net::EmbeddedTestServer* server, ...@@ -108,18 +139,29 @@ void NavigateToFeedAndValidate(net::EmbeddedTestServer* server,
const std::string& expected_feed_title, const std::string& expected_feed_title,
const std::string& expected_item_title, const std::string& expected_item_title,
const std::string& expected_item_desc, const std::string& expected_item_desc,
const std::string& expected_error) { const std::string& expected_error,
std::string expected_msg) {
if (sniff_xml_type) { if (sniff_xml_type) {
// TODO(finnur): Implement this is a non-flaky way. // TODO(finnur): Implement this is a non-flaky way.
} }
content::DOMMessageQueue message_queue;
WebContents* tab = browser->tab_strip_model()->GetActiveWebContents();
NamedFrameCreatedObserver subframe_observer(tab, "preview");
// Navigate to the subscribe page directly. // Navigate to the subscribe page directly.
ui_test_utils::NavigateToURL(browser, ui_test_utils::NavigateToURL(browser,
GetFeedUrl(server, url, true, extension_id)); GetFeedUrl(server, url, true, extension_id));
ASSERT_TRUE(subframe_observer.Wait() != nullptr);
std::string message;
ASSERT_TRUE(message_queue.WaitForMessage(&message));
expected_msg = "\"" + expected_msg + "\"";
EXPECT_STREQ(expected_msg.c_str(), message.c_str());
WebContents* tab = browser->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* frame = content::FrameMatchingPredicate( content::RenderFrameHost* frame = content::FrameMatchingPredicate(
tab, base::Bind(&content::FrameIsChildOfMainFrame)); tab, base::Bind(&content::FrameMatchesName, "preview"));
ASSERT_TRUE(ValidatePageElement( ASSERT_TRUE(ValidatePageElement(
tab->GetMainFrame(), kScriptFeedTitle, expected_feed_title)); tab->GetMainFrame(), kScriptFeedTitle, expected_feed_title));
ASSERT_TRUE(ValidatePageElement(frame, kScriptAnchor, expected_item_title)); ASSERT_TRUE(ValidatePageElement(frame, kScriptAnchor, expected_item_title));
...@@ -146,8 +188,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSMultiRelLink) { ...@@ -146,8 +188,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSMultiRelLink) {
ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1)); ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1));
} }
// This test is flaky on all platforms; see http://crbug.com/340354 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedValidFeed1) {
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed1) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension = LoadExtension( const Extension* extension = LoadExtension(
...@@ -157,11 +198,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed1) { ...@@ -157,11 +198,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed1) {
NavigateToFeedAndValidate(embedded_test_server(), kValidFeed1, browser(), id, NavigateToFeedAndValidate(embedded_test_server(), kValidFeed1, browser(), id,
true, "Feed for MyFeedTitle", "Title 1", "Desc", true, "Feed for MyFeedTitle", "Title 1", "Desc",
"No error"); "No error", "PreviewReady");
} }
// This test is flaky on all platforms; see http://crbug.com/340354 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedValidFeed2) {
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed2) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension = LoadExtension( const Extension* extension = LoadExtension(
...@@ -171,11 +211,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed2) { ...@@ -171,11 +211,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed2) {
NavigateToFeedAndValidate(embedded_test_server(), kValidFeed2, browser(), id, NavigateToFeedAndValidate(embedded_test_server(), kValidFeed2, browser(), id,
true, "Feed for MyFeed2", "My item title1", true, "Feed for MyFeed2", "My item title1",
"This is a summary.", "No error"); "This is a summary.", "No error", "PreviewReady");
} }
// This test is flaky on all platforms; see http://crbug.com/340354 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedValidFeed3) {
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed3) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension = LoadExtension( const Extension* extension = LoadExtension(
...@@ -185,11 +224,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed3) { ...@@ -185,11 +224,11 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed3) {
NavigateToFeedAndValidate(embedded_test_server(), kValidFeed3, browser(), id, NavigateToFeedAndValidate(embedded_test_server(), kValidFeed3, browser(), id,
true, "Feed for Google Code buglist rss feed", true, "Feed for Google Code buglist rss feed",
"My dear title", "My dear content", "No error"); "My dear title", "My dear content", "No error",
"PreviewReady");
} }
// This test is flaky on all platforms; see http://crbug.com/340354 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedValidFeed4) {
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed4) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension = LoadExtension( const Extension* extension = LoadExtension(
...@@ -200,11 +239,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed4) { ...@@ -200,11 +239,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed4) {
NavigateToFeedAndValidate(embedded_test_server(), kValidFeed4, browser(), id, NavigateToFeedAndValidate(embedded_test_server(), kValidFeed4, browser(), id,
true, "Feed for Title chars <script> %23 stop", true, "Feed for Title chars <script> %23 stop",
"Title chars %23 stop", "My dear content %23 stop", "Title chars %23 stop", "My dear content %23 stop",
"No error"); "No error", "PreviewReady");
} }
// This test is flaky on all platforms; see http://crbug.com/340354 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedValidFeed0) {
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed0) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension = LoadExtension( const Extension* extension = LoadExtension(
...@@ -216,11 +254,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed0) { ...@@ -216,11 +254,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed0) {
// trigger a NOTREACHED). // trigger a NOTREACHED).
NavigateToFeedAndValidate(embedded_test_server(), kValidFeed0, browser(), id, NavigateToFeedAndValidate(embedded_test_server(), kValidFeed0, browser(), id,
true, "Feed for MyFeedTitle", "Title 1", true, "Feed for MyFeedTitle", "Title 1",
"Desc VIDEO", "No error"); "Desc VIDEO", "No error", "PreviewReady");
} }
// This test is flaky on all platforms; see http://crbug.com/340354 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedValidFeed5) {
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed5) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension = LoadExtension( const Extension* extension = LoadExtension(
...@@ -232,11 +269,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed5) { ...@@ -232,11 +269,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed5) {
NavigateToFeedAndValidate( NavigateToFeedAndValidate(
embedded_test_server(), kValidFeed5, browser(), id, true, embedded_test_server(), kValidFeed5, browser(), id, true,
"Feed for Unknown feed name", "element 'anchor_0' not found", "Feed for Unknown feed name", "element 'anchor_0' not found",
"element 'desc_0' not found", "This feed contains no entries."); "element 'desc_0' not found", "This feed contains no entries.", "Error");
} }
// This test is flaky on all platforms; see http://crbug.com/340354 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedValidFeed6) {
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed6) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension = LoadExtension( const Extension* extension = LoadExtension(
...@@ -247,9 +283,14 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed6) { ...@@ -247,9 +283,14 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DISABLED_RSSParseFeedValidFeed6) {
// Feed that is technically invalid but still parseable. // Feed that is technically invalid but still parseable.
NavigateToFeedAndValidate(embedded_test_server(), kValidFeed6, browser(), id, NavigateToFeedAndValidate(embedded_test_server(), kValidFeed6, browser(), id,
true, "Feed for MyFeedTitle", "Title 1", "Desc", true, "Feed for MyFeedTitle", "Title 1", "Desc",
"No error"); "No error", "PreviewReady");
} }
// TODO(finnur): Once we're able to Closure-compile (via the Chrome build
// process) the extension along with the HTML sanitizer, we should
// add a test to confirm <img src="foo.jpg" alt="foo" /> is
// preserved after sanitizing (the xkcd test).
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed1) { IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed1) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
...@@ -262,7 +303,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed1) { ...@@ -262,7 +303,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed1) {
NavigateToFeedAndValidate( NavigateToFeedAndValidate(
embedded_test_server(), kInvalidFeed1, browser(), id, false, embedded_test_server(), kInvalidFeed1, browser(), id, false,
"Feed for Unknown feed name", "element 'anchor_0' not found", "Feed for Unknown feed name", "element 'anchor_0' not found",
"element 'desc_0' not found", "This feed contains no entries."); "element 'desc_0' not found", "This feed contains no entries.", "Error");
} }
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed2) { IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed2) {
...@@ -277,7 +318,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed2) { ...@@ -277,7 +318,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed2) {
NavigateToFeedAndValidate( NavigateToFeedAndValidate(
embedded_test_server(), kInvalidFeed2, browser(), id, false, embedded_test_server(), kInvalidFeed2, browser(), id, false,
"Feed for Unknown feed name", "element 'anchor_0' not found", "Feed for Unknown feed name", "element 'anchor_0' not found",
"element 'desc_0' not found", "This feed contains no entries."); "element 'desc_0' not found", "This feed contains no entries.", "Error");
} }
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed3) { IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed3) {
...@@ -292,7 +333,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed3) { ...@@ -292,7 +333,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed3) {
NavigateToFeedAndValidate( NavigateToFeedAndValidate(
embedded_test_server(), "/foo.xml", browser(), id, false, embedded_test_server(), "/foo.xml", browser(), id, false,
"Feed for Unknown feed name", "element 'anchor_0' not found", "Feed for Unknown feed name", "element 'anchor_0' not found",
"element 'desc_0' not found", "This feed contains no entries."); "element 'desc_0' not found", "This feed contains no entries.", "Error");
} }
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed4) { IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed4) {
...@@ -313,12 +354,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed4) { ...@@ -313,12 +354,10 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedInvalidFeed4) {
NavigateToFeedAndValidate( NavigateToFeedAndValidate(
embedded_test_server(), kFeedTripleEncoded, browser(), id, true, embedded_test_server(), kFeedTripleEncoded, browser(), id, true,
"Feed for Unknown feed name", "element 'anchor_0' not found", "Feed for Unknown feed name", "element 'anchor_0' not found",
"element 'desc_0' not found", "This feed contains no entries."); "element 'desc_0' not found", "This feed contains no entries.", "Error");
} }
// This test is flaky on all platforms; see http://crbug.com/340354 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, RSSParseFeedValidFeedNoLinks) {
IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest,
DISABLED_RSSParseFeedValidFeedNoLinks) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension = LoadExtension( const Extension* extension = LoadExtension(
...@@ -329,7 +368,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, ...@@ -329,7 +368,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest,
// Valid feed but containing no links. // Valid feed but containing no links.
NavigateToFeedAndValidate(embedded_test_server(), kValidFeedNoLinks, NavigateToFeedAndValidate(embedded_test_server(), kValidFeedNoLinks,
browser(), id, true, "Feed for MyFeedTitle", browser(), id, true, "Feed for MyFeedTitle",
"Title with no link", "Desc", "No error"); "Title with no link", "Desc", "No error",
"PreviewReady");
} }
} // namespace extensions } // namespace extensions
#!/bin/bash
# Copyright 2020 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.
closure_path=../../../../../third_party/google-closure-library
compiler_jar=compiler/closure-compiler-v20200614.jar
echo
echo '-------------------------------------------'
echo 'Building the RSS extension.'
echo '-------------------------------------------'
echo
echo 'Recreating out/ from scratch...'
rm -rf out/
mkdir out/
echo 'Copying manifest.json' && cp -r src/manifest.json out/
echo Copying _locales && cp -r src/_locales out/_locales
echo Copying .html && cp src/*.html out/
echo Copying .css && cp src/*.css out/
echo Copying .png && cp src/*.png out/
echo 'Copying javascipt files (all except iframe.js, which will be compiled).'
echo 'Copying background.js' && cp src/background.js out/background.js
echo 'Copying common.js' && cp src/common.js out/common.js
echo 'Copying doc_start.js' && cp src/doc_start.js out/doc_start.js
echo 'Copying feed_finder.js' && cp src/feed_finder.js out/feed_finder.js
echo 'Copying options.js' && cp src/options.js out/options.js
echo 'Copying popup.js' && cp src/popup.js out/popup.js
echo 'Copying sniff_common.js' && cp src/sniff_common.js out/sniff_common.js
echo 'Copying subscribe.js' && cp src/subscribe.js out/subscribe.js
# NOTE: test_*.js files should not be copied over.
echo
# Uncomment to see hashes for scripts.
# echo 'SHA hashes:'
# echo 'subscribe.js sha256-'$(cat src/subscribe.js | openssl dgst -sha256 -binary | openssl enc -base64)
# echo 'freeflow foo sha256-'$(echo -n "console.log('foo');" | openssl dgst -sha256 -binary | openssl enc -base64)
# echo
echo 'Closure compiling:'
echo 'Using' $compiler_jar
echo ' ^ Make sure this is present and up to date.'
echo 'Compiling iframe.js.'
$closure_path/closure/bin/build/closurebuilder.py \
--root=src/ \
--root=$closure_path \
--namespace="RSSExtension.IFrame" \
--output_mode=compiled \
--compiler_jar=$compiler_jar \
> out/iframe.js
echo 'The contents of out/ now contain the extension to be uploaded.'
echo
echo 'Extension:'
grep "\"version\"" out/manifest.json
echo ' ^ Please make sure this version is correct!!'
...@@ -11,8 +11,8 @@ var storageEnabled = window.localStorage != null; ...@@ -11,8 +11,8 @@ var storageEnabled = window.localStorage != null;
function defaultReaderList() { function defaultReaderList() {
// This is the default list, unless replaced by what was saved previously. // This is the default list, unless replaced by what was saved previously.
return [ return [
{ 'url': 'http://www.bloglines.com/login?r=/sub/%s', { 'url': 'http://www.newsblur.com/?url=%s',
'description': 'Bloglines' 'description': 'Newsblur'
}, },
{ 'url': 'http://add.my.yahoo.com/rss?url=%s', { 'url': 'http://add.my.yahoo.com/rss?url=%s',
'description': 'My Yahoo' 'description': 'My Yahoo'
......
/* Copyright (c) 2009 The Chromium Authors. All rights reserved. // Copyright (c) 2009 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. // found in the LICENSE file.
*/
goog.provide('RSSExtension.IFrame');
/* Use only multi-line comments in this file, since during testing
its contents will get read from disk and stuffed into the goog.require('goog.html.sanitizer.HtmlSanitizer');
iframe .src tag, which is a process that doesn't preserve line goog.require('goog.dom.safe');
breaks and makes single-line comments comment out the rest of the code.
*/ // The maximum number of feed items to show in the preview.
/* The maximum number of feed items to show in the preview. */
var maxFeedItems = 10; var maxFeedItems = 10;
/* The maximum number of characters to show in the feed item title. */ // The maximum number of characters to show in the feed item title.
var maxTitleCount = 1024; var maxTitleCount = 1024;
/* Find the token and target origin for this conversation from the HTML. The // Find the token and target origin for this conversation from the HTML. The
token is used for secure communication, and is generated and stuffed into the // token is used for secure communication, and is generated and stuffed into the
frame by subscribe.js. // frame by subscribe.js.
*/
var token = ''; var token = '';
var targetOrigin = ''; var targetOrigin = '';
var html = document.documentElement.outerHTML; var html = document.documentElement.outerHTML;
...@@ -45,20 +42,20 @@ if (token.length > 0) { ...@@ -45,20 +42,20 @@ if (token.length > 0) {
if (doc) { if (doc) {
buildPreview(doc); buildPreview(doc);
} else { } else {
/* Already handled in subscribe.html */ // Already handled in subscribe.html.
} }
} }
} }
function buildPreview(doc) { function buildPreview(doc) {
/* Start building the part we render inside an IFRAME. We use a table to // Start building the part we render inside an IFRAME. We use a table to
ensure that items are separated vertically from each other. */ // ensure that items are separated vertically from each other.
var table = document.createElement("table"); var table = document.createElement("table");
var tbody = document.createElement("tbody"); var tbody = document.createElement("tbody");
table.appendChild(tbody); table.appendChild(tbody);
/* Now parse the rest. Some use <entry> for each feed item, others use // Now parse the rest. Some use <entry> for each feed item, others use
<channel><item>. */ // <channel><item>.
var entries = doc.getElementsByTagName('entry'); var entries = doc.getElementsByTagName('entry');
if (entries.length == 0) if (entries.length == 0)
entries = doc.getElementsByTagName('item'); entries = doc.getElementsByTagName('item');
...@@ -66,19 +63,19 @@ function buildPreview(doc) { ...@@ -66,19 +63,19 @@ function buildPreview(doc) {
for (i = 0; i < entries.length && i < maxFeedItems; ++i) { for (i = 0; i < entries.length && i < maxFeedItems; ++i) {
item = entries.item(i); item = entries.item(i);
/* Grab the title for the feed item. */ // Grab the title for the feed item.
var itemTitle = item.getElementsByTagName('title')[0]; var itemTitle = item.getElementsByTagName('title')[0];
if (itemTitle) if (itemTitle)
itemTitle = itemTitle.textContent; itemTitle = itemTitle.textContent;
else else
itemTitle = "Unknown title"; itemTitle = "Unknown title";
/* Ensure max length for title. */ // Ensure max length for title.
if (itemTitle.length > maxTitleCount) if (itemTitle.length > maxTitleCount)
itemTitle = itemTitle.substring(0, maxTitleCount) + "..."; itemTitle = itemTitle.substring(0, maxTitleCount) + "...";
/* Grab the description. // Grab the description.
TODO(aa): Do we need to check for type=html here? */ // TODO(aa): Do we need to check for type=html here?
var itemDesc = item.getElementsByTagName('description')[0]; var itemDesc = item.getElementsByTagName('description')[0];
if (!itemDesc) if (!itemDesc)
itemDesc = item.getElementsByTagName('summary')[0]; itemDesc = item.getElementsByTagName('summary')[0];
...@@ -90,7 +87,7 @@ function buildPreview(doc) { ...@@ -90,7 +87,7 @@ function buildPreview(doc) {
else else
itemDesc = ""; itemDesc = "";
/* Grab the link URL. */ // Grab the link URL.
var itemLink = item.getElementsByTagName('link'); var itemLink = item.getElementsByTagName('link');
var link = ""; var link = "";
if (itemLink.length > 0) { if (itemLink.length > 0) {
...@@ -104,21 +101,28 @@ function buildPreview(doc) { ...@@ -104,21 +101,28 @@ function buildPreview(doc) {
var tr = document.createElement("tr"); var tr = document.createElement("tr");
var td = document.createElement("td"); var td = document.createElement("td");
/* If we found a link we'll create an anchor element, var sanitizer = window.domAutomationController === undefined ?
otherwise just use a bold headline for the title. */ new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
.withCustomUrlPolicy(goog.html.SafeUrl.sanitize)
.build()
: goog.html.sanitizer.HtmlSanitizer;
// If we found a link we'll create an anchor element,
// otherwise just use a bold headline for the title.
var anchor = (link != "") ? document.createElement("a") : var anchor = (link != "") ? document.createElement("a") :
document.createElement("strong"); document.createElement("strong");
anchor.id = "anchor_" + String(i); anchor.id = "anchor_" + String(i);
if (link != "") if (link != "")
anchor.href = link; goog.dom.safe.setAnchorHref(anchor, goog.html.SafeUrl.sanitize(link));
anchor.innerHTML = itemTitle; goog.dom.safe.setInnerHtml(anchor, sanitizer.sanitize(itemTitle));
anchor.target = "_top"; anchor.target = "_top";
anchor.className = "item_title"; anchor.className = "item_title";
var span = document.createElement("span"); var span = document.createElement("span");
span.id = "desc_" + String(i); span.id = "desc_" + String(i);
span.className = "item_desc"; span.className = "item_desc";
span.innerHTML = itemDesc; goog.dom.safe.setInnerHtml(span, sanitizer.sanitize(itemDesc));
td.appendChild(anchor); td.appendChild(anchor);
td.appendChild(document.createElement("br")); td.appendChild(document.createElement("br"));
...@@ -132,4 +136,8 @@ function buildPreview(doc) { ...@@ -132,4 +136,8 @@ function buildPreview(doc) {
table.appendChild(tbody); table.appendChild(tbody);
document.body.appendChild(table); document.body.appendChild(table);
if (window.domAutomationController) {
window.domAutomationController.send('PreviewReady');
}
} }
...@@ -25,6 +25,6 @@ ...@@ -25,6 +25,6 @@
"default_title": "__MSG_rss_subscription_default_title__" "default_title": "__MSG_rss_subscription_default_title__"
}, },
"permissions": [ "tabs", "http://*/*", "https://*/*", "storage" ], "permissions": [ "tabs", "http://*/*", "https://*/*", "storage" ],
"version": "2.2.3", "version": "2.2.5",
"web_accessible_resources": [ "iframe.js", "style.css" ] "web_accessible_resources": [ "iframe.js", "style.css" ]
} }
...@@ -12,14 +12,6 @@ var feedUrl = decodeURIComponent(queryString[0]); ...@@ -12,14 +12,6 @@ var feedUrl = decodeURIComponent(queryString[0]);
// This extension's ID. // This extension's ID.
var extension_id = chrome.i18n.getMessage("@@extension_id"); var extension_id = chrome.i18n.getMessage("@@extension_id");
// During testing, we cannot load the iframe and stylesheet from an external
// source, so we allow loading them synchronously and stuff the frame with the
// results. Since this is only allowed during testing, it isn't supported for
// the official extension ID.
var synchronousRequest =
extension_id != "nlbjncdgjeocebhnmkbbbdekmmmcbfjd" ?
queryString[1] == "synchronous" : false;
// The XMLHttpRequest object that tries to load and parse the feed, and (if // The XMLHttpRequest object that tries to load and parse the feed, and (if
// testing) also the style sheet and the frame js. // testing) also the style sheet and the frame js.
var req; var req;
...@@ -96,38 +88,19 @@ function main() { ...@@ -96,38 +88,19 @@ function main() {
crypto.getRandomValues(tokenArray); crypto.getRandomValues(tokenArray);
token = [].join.call(tokenArray); token = [].join.call(tokenArray);
// Now fetch the data.
req = new XMLHttpRequest();
if (synchronousRequest) {
// Tests that load the html page directly through a file:// url don't have
// access to the js and css from the frame so we must load them first and
// inject them into the src for the iframe.
req.open("GET", "style.css", false);
req.send(null);
styleSheet = "<style>" + req.responseText + "</style>";
req.open("GET", "iframe.js", false);
req.send(null);
if (req.responseText.indexOf('//') != -1) {
console.log('Error: Single-line comment(s) found in iframe.js');
} else {
frameScript = "<script>" +
req.responseText +
"<" + "/script>";
}
} else {
// Normal loading just requires links to the css and the js file.
styleSheet = "<link rel='stylesheet' type='text/css' href='" + styleSheet = "<link rel='stylesheet' type='text/css' href='" +
chrome.extension.getURL("style.css") + "'>"; chrome.extension.getURL("style.css") + "'>";
frameScript = "<script src='" + chrome.extension.getURL("iframe.js") + frameScript = window.domAutomationController !== undefined ? "<script src='" +
chrome.extension.getURL("test_support.js") +
"'></" + "script>" : "";
frameScript += "<script src='" + chrome.extension.getURL("iframe.js") +
"'></" + "script>"; "'></" + "script>";
}
// Now fetch the feed data.
req = new XMLHttpRequest();
req.onload = handleResponse; req.onload = handleResponse;
req.onerror = handleError; req.onerror = handleError;
req.open("GET", feedUrl, !synchronousRequest); req.open("GET", feedUrl, true);
// Not everyone sets the mime type correctly, which causes handleResponse // Not everyone sets the mime type correctly, which causes handleResponse
// to fail to XML parse the response text from the server. By forcing // to fail to XML parse the response text from the server. By forcing
// it to text/xml we avoid this. // it to text/xml we avoid this.
...@@ -157,6 +130,10 @@ function handleFeedParsingFailed(error) { ...@@ -157,6 +130,10 @@ function handleFeedParsingFailed(error) {
// The tests always expect an IFRAME, so add one showing the error. // The tests always expect an IFRAME, so add one showing the error.
var html = "<body><span id=\"error\" class=\"item_desc\">" + error + var html = "<body><span id=\"error\" class=\"item_desc\">" + error +
"</span></body>"; "</span></body>";
if (window.domAutomationController) {
html += "<script src='" + chrome.extension.getURL("test_send_error.js") +
"'></" + "script>";
}
var error_frame = createFrame('error', html); var error_frame = createFrame('error', html);
var itemsTag = document.getElementById('items'); var itemsTag = document.getElementById('items');
...@@ -164,15 +141,12 @@ function handleFeedParsingFailed(error) { ...@@ -164,15 +141,12 @@ function handleFeedParsingFailed(error) {
} }
function createFrame(frame_id, html) { function createFrame(frame_id, html) {
// During testing, we stuff the iframe with the script directly, so we relax var csp = '<meta http-equiv="content-security-policy" ' +
// the policy on running scripts under that scenario.
var csp = synchronousRequest ?
'<meta http-equiv="content-security-policy" ' +
'content="object-src \'none\'">' :
'<meta http-equiv="content-security-policy" ' +
'content="object-src \'none\'; script-src \'self\'">'; 'content="object-src \'none\'; script-src \'self\'">';
frame = document.createElement('iframe'); frame = document.createElement('iframe');
frame.id = frame_id; frame.id = frame_id;
frame.name = "preview";
frame.sandbox = "allow-scripts";
frame.src = "data:text/html;charset=utf-8,<html>" + csp + frame.src = "data:text/html;charset=utf-8,<html>" + csp +
"<!--Token:" + extension_id + token + "<!--Token:" + extension_id + token +
"-->" + html + "</html>"; "-->" + html + "</html>";
......
// Copyright 2020 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.
// Used to communicate feed parsing errors to the testing framework.
if (window.domAutomationController) {
window.domAutomationController.send('Error');
}
// Copyright 2020 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.
// Provides a default implementation for the sanitizer (a pass-through), so that
// tests can be run on the extension directly (without having to Closure compile
// it first).
var goog = {
dom: {
safe: {
setAnchorHref: function(element, href) {
element.href = href;
},
setInnerHtml: function(element, html) {
element.innerHTML = html;
}
}
},
html: {
sanitizer: {
HtmlSanitizer: {
sanitize: function(data) {
return data;
}
}
},
SafeUrl: {
sanitize: function(data) {
return data;
}
}
},
provide: function(namespace) {},
require: function(namespace) {}
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment