chromium/chrome/test/data/extensions/api_test/declarative_net_request/on_rules_matched_debug/background.js

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

// Navigates to |url| and invokes |callback| when the navigation is complete.
function navigateTab(url, expectedTabUrl, callback) {
  chrome.tabs.onUpdated.addListener(function updateCallback(_, info, tab) {
    if (info.status == 'complete' && tab.url == expectedTabUrl) {
      chrome.tabs.onUpdated.removeListener(updateCallback);
      callback(tab);
    }
  });

  chrome.tabs.update({url: url});
}

var matchedRules = [];
var onRuleMatchedDebugCallback = (rule) => {
  matchedRules.push(rule);
};

var testServerPort;
function getServerURL(host) {
  if (!testServerPort)
    throw new Error('Called getServerURL outside of runTests.');
  return `http://${host}:${testServerPort}/`;
}

function resetMatchedRules() {
  matchedRules = [];
}

function verifyExpectedRuleInfo(expectedRuleInfo) {
  chrome.test.assertEq(1, matchedRules.length);
  const matchedRule = matchedRules[0];

  // The request ID may not be known but should be populated.
  chrome.test.assertTrue(matchedRule.request.hasOwnProperty('requestId'));
  delete matchedRule.request.requestId;
  chrome.test.assertFalse(matchedRule.request.hasOwnProperty('documentId'));

  // Remove frame/parentFrameId/parentDocumentId if not set in the
  // expectedRules. The values of these fields vary depending on what type of
  // extension ContextType is used.
  if (!('frameId' in expectedRuleInfo.request)) {
    delete matchedRule.request.frameId;
  }
  if (!('parentFrameId' in expectedRuleInfo.request)) {
    delete matchedRule.request.parentFrameId;
  }
  if (!('parentDocumentId' in expectedRuleInfo.request)) {
    delete matchedRule.request.parentDocumentId;
  }

  chrome.test.assertEq(expectedRuleInfo, matchedRule);
}

var tests = [
  function setup() {
    chrome.declarativeNetRequest.onRuleMatchedDebug.addListener(
        onRuleMatchedDebugCallback);

    // This test was known to flake as reported in crbug.com/1029233 due to a
    // race condition where a request was sent and a declarative rule was
    // matched before the onRuleMatchedDebug listener was properly added.
    // Sending the request after waiting for a round trip should fix the
    // flakiness.
    chrome.test.waitForRoundTrip('msg', chrome.test.succeed);
  },

  function testDynamicRule() {
    resetMatchedRules();

    const rule = {
      id: 1,
      priority: 1,
      condition: {urlFilter: 'def', 'resourceTypes': ['main_frame']},
      action: {type: 'block'},
    };

    chrome.declarativeNetRequest.updateDynamicRules(
        {addRules: [rule]}, function() {
          chrome.test.assertNoLastError();
          const url = getServerURL('def.com');
          navigateTab(url, url, (tab) => {
            const expectedRuleInfo = {
              request: {
                initiator: `chrome-extension://${chrome.runtime.id}`,
                method: 'GET',
                documentLifecycle: 'active',
                frameType: 'outermost_frame',
                frameId: 0,
                parentFrameId: -1,
                tabId: tab.id,
                type: 'main_frame',
                url: url
              },
              rule: {
                ruleId: 1,
                rulesetId: chrome.declarativeNetRequest.DYNAMIC_RULESET_ID
              }
            };
            verifyExpectedRuleInfo(expectedRuleInfo);
            chrome.test.succeed();
          });
        });
  },

  function testBlockRule() {
    resetMatchedRules();

    const url = getServerURL('abc.com');
    navigateTab(url, url, (tab) => {
      const expectedRuleInfo = {
        request: {
          initiator: `chrome-extension://${chrome.runtime.id}`,
          method: 'GET',
          documentLifecycle: 'active',
          frameType: 'outermost_frame',
          frameId: 0,
          parentFrameId: -1,
          tabId: tab.id,
          type: 'main_frame',
          url: url
        },
        rule: {ruleId: 1, rulesetId: 'rules1'}
      };
      verifyExpectedRuleInfo(expectedRuleInfo);
      chrome.test.succeed();
    });
  },

  // Ensure that requests that don't originate from a tab (such as those from
  // the extension background page) trigger the listener.
  function testBackgroundPageRequest() {
    function listenOnce(target, callback) {
      let innerCallback = function(info) {
        target.removeListener(innerCallback);
        callback(info);
      };
      target.addListener(innerCallback);
    }

    listenOnce(
        chrome.declarativeNetRequest.onRuleMatchedDebug,
        function(fetchPromise, info) {
          fetchPromise
              .then(function(response) {
                chrome.test.fail('Request should be blocked by rule with ID 1');
              })
              .catch(function(error) {
                chrome.test.assertEq(1, info.rule.ruleId);
                chrome.test.assertEq('rules1', info.rule.rulesetId);

                // Tab ID should be -1 since this request was made from a
                // background page.
                chrome.test.assertEq(-1, info.request.tabId);
                chrome.test.succeed();
              });
        }.bind(null, fetch(getServerURL('abc.com'), {method: 'GET'})));
  },

  function testNoRuleMatched() {
    resetMatchedRules();
    const url = getServerURL('nomatch.com');
    navigateTab(url, url, (tab) => {
      chrome.test.assertEq(0, matchedRules.length);
      chrome.test.succeed();
    });
  },

  function testAllowRule() {
    resetMatchedRules();

    const url = getServerURL('abcde.com');
    navigateTab(url, url, (tab) => {
      // The allow rule should not be matched twice despite it overriding both
      // a block and a redirect rule (rules with id 1 and 5).
      chrome.test.assertEq(1, matchedRules.length);
      const matchedRule = matchedRules[0];
      chrome.test.assertEq(4, matchedRule.rule.ruleId);
      chrome.test.assertEq('rules2', matchedRule.rule.rulesetId);

      chrome.test.succeed();
    });
  },

  function testMultipleRules() {
    resetMatchedRules();

    // redir1.com --> redir2.com --> abc.com (blocked)
    // 3 rules are matched from the above sequence of actions.
    navigateTab(getServerURL('redir1.com'), 'http://abc.com/', (tab) => {
      chrome.test.assertEq(3, matchedRules.length);

      const expectedMatches = [
        {ruleId: 2, rulesetId: 'rules1'}, {ruleId: 3, rulesetId: 'rules2'},
        {ruleId: 1, rulesetId: 'rules1'}
      ];
      for (let i = 0; i < matchedRules.length; ++i) {
        chrome.test.assertEq(
            expectedMatches[i].ruleId, matchedRules[i].rule.ruleId);
        chrome.test.assertEq(
            expectedMatches[i].rulesetId, matchedRules[i].rule.rulesetId);
      }

      chrome.test.succeed();
    });
  },

  // Makes sure loads inside sandboxed iframes are considered thirdParty.
  function testSandboxedFrame() {
    resetMatchedRules();

    const baseUrl = getServerURL('a.com') +
        'extensions/api_test/declarative_net_request/on_rules_matched_debug/';
    const url = baseUrl + 'sandbox_container.html';
    const innerFrameUrl = baseUrl + 'blocked_frame.html';

    // Initiator inside sandbox is opaque.
    const kOpaqueInitiator = 'null';

    navigateTab(url, url, (tab) => {
      const expectedRuleInfo = {
        request: {
          documentLifecycle: 'active',
          frameType: 'sub_frame',
          initiator: kOpaqueInitiator,
          method: 'GET',
          type: 'sub_frame',
          tabId: tab.id,
          url: innerFrameUrl
        },
        rule: {ruleId: 6, rulesetId: 'rules2'}
      };
      verifyExpectedRuleInfo(expectedRuleInfo);
      chrome.test.succeed();
    });
  }
];

chrome.test.getConfig(function(config) {
  testServerPort = config.testServer.port;
  chrome.test.runTests(tests);
});