chromium/chrome/test/data/extensions/subscribe_page_action_v3/src/subscribe.js

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Grab the querystring, removing question mark at the front and splitting on
// the ampersand.
var queryString = location.search.substring(1).split("&");

// The feed URL is the first component and always present.
var feedUrl = decodeURIComponent(queryString[0]);

// This extension's ID.
var extension_id = chrome.i18n.getMessage("@@extension_id");

// The XMLHttpRequest object that tries to load and parse the feed, and (if
// testing) also the style sheet and the frame js.
var req;

// Depending on whether this is run from a test or from the extension, this
// will either be a link to the css file within the extension or contain the
// contents of the style sheet, fetched through XmlHttpRequest.
var styleSheet = "";

// Depending on whether this is run from a test or from the extension, this
// will either be a link to the js file within the extension or contain the
// contents of the style sheet, fetched through XmlHttpRequest.
var frameScript = "";

// What to show when we cannot parse the feed name.
var unknownName = chrome.i18n.getMessage("rss_subscription_unknown_feed_name");

// A list of feed readers, populated by localStorage if available, otherwise
// hard coded.
var feedReaderList;

// The token to use during communications with the iframe.
var token = "";

// Navigates to the reader of the user's choice (for subscribing to the feed).
function navigate() {
  var select = document.getElementById('readerDropdown');
  var url =
      feedReaderList[select.selectedIndex].url.replace(
          "%s", encodeURIComponent(feedUrl));

  // Before we navigate, see if we want to skip this step in the future...
  if (storageEnabled) {
    // See if the user wants to always use this reader.
    var alwaysUse = document.getElementById('alwaysUse');
    if (alwaysUse.checked) {
      window.localStorage.defaultReader =
          feedReaderList[select.selectedIndex].url;
      window.localStorage.showPreviewPage = "No";
    }
  }

  document.location = url;
}

/**
* The main function. Sets up the selection list for possible readers and
* fetches the data.
*/
function main() {
  if (storageEnabled && window.localStorage.readerList)
      feedReaderList = JSON.parse(window.localStorage.readerList);
  if (!feedReaderList)
    feedReaderList = defaultReaderList();

  // Populate the list of readers.
  var readerDropdown = document.getElementById('readerDropdown');
  for (i = 0; i < feedReaderList.length; ++i) {
    readerDropdown.options[i] = new Option(feedReaderList[i].description, i);
    if (storageEnabled && isDefaultReader(feedReaderList[i].url))
      readerDropdown.selectedIndex = i;
  }

  if (storageEnabled) {
    // Add the "Manage..." entry to the dropdown and show the checkbox asking
    // if we always want to use this reader in the future (skip the preview).
    readerDropdown.options[i] =
        new Option(chrome.i18n.getMessage("rss_subscription_manage_label"), "");
    document.getElementById('alwaysUseSpan').style.display = "block";
  }

  // Set the token.
  var tokenArray  = new Uint32Array(4);
  crypto.getRandomValues(tokenArray);
  token = [].join.call(tokenArray);

  styleSheet = "<link rel='stylesheet' type='text/css' href='" +
                   chrome.runtime.getURL("style.css") + "'>";
  frameScript = window.domAutomationController !== undefined ? "<script src='" +
                    chrome.runtime.getURL("test_support.js") +
                    "'></" + "script>" : "";
  frameScript += "<script src='" + chrome.runtime.getURL("iframe.js") +
                     "'></" + "script>";

  // Now fetch the feed data.
  req = new XMLHttpRequest();
  req.onload = handleResponse;
  req.onerror = handleError;
  req.open("GET", feedUrl, true);
  // Not everyone sets the mime type correctly, which causes handleResponse
  // to fail to XML parse the response text from the server. By forcing
  // it to text/xml we avoid this.
  req.overrideMimeType('text/xml');
  req.send(null);

  document.getElementById('feedUrl').href = 'view-source:' + feedUrl;
}

// Sets the title for the feed.
function setFeedTitle(title) {
  var titleTag = document.getElementById('title');
  titleTag.textContent =
      chrome.i18n.getMessage("rss_subscription_feed_for", title);
}

// Handles errors during the XMLHttpRequest.
function handleError() {
  handleFeedParsingFailed(
      chrome.i18n.getMessage("rss_subscription_error_fetching"));
}

// Handles feed parsing errors.
function handleFeedParsingFailed(error) {
  setFeedTitle(unknownName);

  // The tests always expect an IFRAME, so add one showing the error.
  var html = "<body><span id=\"error\" class=\"item_desc\">" + error +
               "</span></body>";
  if (window.domAutomationController) {
    html += "<script src='" + chrome.runtime.getURL("test_send_error.js") +
                     "'></" + "script>";
  }

  var error_frame = createFrame('error', html);
  var itemsTag = document.getElementById('items');
  itemsTag.appendChild(error_frame);
}

function createFrame(frame_id, html) {
  var csp = '<meta http-equiv="content-security-policy" ' +
          'content="object-src \'none\'; script-src \'self\'">';
  frame = document.createElement('iframe');
  frame.id = frame_id;
  frame.name = "preview";
  frame.sandbox = "allow-scripts";
  frame.src = "data:text/html;charset=utf-8,<html>" + csp +
              "<!--Token:" + extension_id + token +
              "-->" + html + "</html>";
  frame.scrolling = "auto";
  frame.frameBorder = "0";
  frame.marginWidth = "0";
  return frame;
}

// Handles parsing the feed data we got back from XMLHttpRequest.
function handleResponse() {
  // Uncomment these three lines to see what the feed data looks like.
  // var itemsTag = document.getElementById('items');
  // itemsTag.textContent = req.responseText;
  // return;

  var doc = req.responseXML;
  if (!doc) {
    // If the XMLHttpRequest object fails to parse the feed we make an attempt
    // ourselves, because sometimes feeds have html/script code appended below a
    // valid feed, which makes the feed invalid as a whole even though it is
    // still parsable.
    var domParser = new DOMParser();
    doc = domParser.parseFromString(req.responseText, "text/xml");
    if (!doc) {
      handleFeedParsingFailed(
          chrome.i18n.getMessage("rss_subscription_not_valid_feed"));
      return;
    }
  }

  // We must find at least one 'entry' or 'item' element before proceeding.
  var entries = doc.getElementsByTagName('entry');
  if (entries.length == 0)
    entries = doc.getElementsByTagName('item');
  if (entries.length == 0) {
    handleFeedParsingFailed(
        chrome.i18n.getMessage("rss_subscription_no_entries"))
    return;
  }

  // Figure out what the title of the whole feed is.
  var title = doc.getElementsByTagName('title')[0];
  if (title)
    setFeedTitle(title.textContent);
  else
    setFeedTitle(unknownName);

  // Embed the iframe.
  var itemsTag = document.getElementById('items');
  // TODO(aa): Add base URL tag
  iframe = createFrame('rss', styleSheet + frameScript);
  itemsTag.appendChild(iframe);
}

function handleCopyToClipboard() {
  navigator.clipboard.writeText('view-source:' + feedUrl);

  let copyTrigger = document.getElementById('feedUrl');
  copyTrigger.innerText = "Feed URL copied to clipboard";
}

/**
* Handler for when selection changes.
*/
function onSelectChanged() {
  if (!storageEnabled)
    return;
  var readerDropdown = document.getElementById('readerDropdown');

  // If the last item (Manage...) was selected we show the options.
  var oldSelection = readerDropdown.selectedIndex;
  if (readerDropdown.selectedIndex == readerDropdown.length - 1)
    window.location = "options.html";
}

document.addEventListener('DOMContentLoaded', function () {
  document.title =
      chrome.i18n.getMessage("rss_subscription_default_title");
  i18nReplace('rss_subscription_subscribe_using');
  i18nReplace('rss_subscription_subscribe_button');
  i18nReplace('rss_subscription_always_use');
  i18nReplace('rss_subscription_feed_preview');
  i18nReplaceImpl('feedUrl', 'rss_subscription_feed_link', '');

  var dropdown = document.getElementById('readerDropdown');
  dropdown.addEventListener('change', onSelectChanged);
  var button = document.getElementById('rss_subscription_subscribe_button');
  button.addEventListener('click', navigate);
  var copyLink = document.getElementById('feedUrl');
  copyLink.addEventListener('click', handleCopyToClipboard);

  main();
});

window.addEventListener("message", function(e) {
  if (e.ports[0] && e.data === token)
    e.ports[0].postMessage(req.responseText);
}, false);