text:
namespace {

using blink::mojom::WebFeature;
using testing::IsEmpty;

// We use a custom page that explicitly disables its own favicon (by providing
// an invalid data: URL for it) so as to prevent the browser from making an
// automatic request to /favicon.ico. This is because the automatic request
// messes with our tests, in which we want to trigger a single request from the
// web page to a resource of our choice and observe the side-effect in metrics.
constexpr char kNoFaviconPath[] = "/private_network_access/no-favicon.html";

// Same as kNoFaviconPath, except it carries a header that makes the browser
// consider it came from the `public` address space, irrespective of the fact
// that we loaded the web page from localhost.
constexpr char kTreatAsPublicAddressPath[] =
    "/private_network_access/no-favicon-treat-as-public-address.html";

GURL SecureURL(const net::EmbeddedTestServer& server, const std::string& path) {
  // Test HTTPS servers cannot lie about their hostname, so they yield URLs
  // starting with https://localhost. http://localhost is already a secure
  // context, so we do not bother instantiating an HTTPS server.
  return server.GetURL(path);
}

GURL NonSecureURL(const net::EmbeddedTestServer& server,
                  const std::string& path) {
  return server.GetURL("foo.test", path);
}

GURL LocalSecureURL(const net::EmbeddedTestServer& server) {
  return SecureURL(server, kNoFaviconPath);
}

GURL LocalNonSecureURL(const net::EmbeddedTestServer& server) {
  return NonSecureURL(server, kNoFaviconPath);
}

GURL PublicSecureURL(const net::EmbeddedTestServer& server) {
  return SecureURL(server, kTreatAsPublicAddressPath);
}

GURL PublicNonSecureURL(const net::EmbeddedTestServer& server) {
  return NonSecureURL(server, kTreatAsPublicAddressPath);
}

// Similar to LocalNonSecure() but can be fetched by any origin.
GURL LocalNonSecureWithCrossOriginCors(const net::EmbeddedTestServer& server) {
  return SecureURL(server, "/cors-ok.txt");
}

// Path to a worker script that posts a message to its creator once loaded.
constexpr char kWorkerScriptPath[] = "/workers/post_ready.js";

// Same as above, but with PNA headers set correctly for preflight requests.
constexpr char kWorkerScriptWithPnaHeadersPath[] =
    "/workers/post_ready_with_pna_headers.js";

// The returned script evaluates to a boolean indicating whether the fetch
// succeeded or not.
std::string FetchScript(const GURL& url) {
  return content::JsReplace(
      "fetch($1).then(response => true).catch(error => false)", url);
}

std::string FetchWorkerScript(base::StringPiece relative_url) {
  constexpr char kTemplate[] = R"(
    new Promise((resolve) => {
      const worker = new Worker($1);
      worker.addEventListener("message", () => { resolve(true); });
      worker.addEventListener("error", () => { resolve(false); });
    });
  )";
  return content::JsReplace(kTemplate, relative_url);
}

// Path to a worker script that posts a message to each client that connects.
constexpr char kSharedWorkerScriptPath[] = "/workers/shared_post_ready.js";

// Same as above, but with PNA headers set correctly for preflight requests.
constexpr char kSharedWorkerScriptWithPnaHeadersPath[] =
    "/workers/shared_post_ready_with_pna_headers.js";

// Instantiates a shared worker script from `path`.
// If it loads successfully, the worker should post a message to each client
// that connects to it to signal success.
std::string FetchSharedWorkerScript(base::StringPiece path) {
  constexpr char kTemplate[] = R"(
    new Promise((resolve) => {
      const worker = new SharedWorker($1);
      worker.port.addEventListener("message", () => resolve(true));
      worker.addEventListener("error", () => resolve(false));
      worker.port.start();
    })
  )";

  return content::JsReplace(kTemplate, path);
}

std::vector<WebFeature> AllAddressSpaceFeatures() {
  return {
      WebFeature::kAddressSpacePrivateSecureContextEmbeddedLocal,
      WebFeature::kAddressSpacePrivateNonSecureContextEmbeddedLocal,
      WebFeature::kAddressSpacePublicSecureContextEmbeddedLocal,
      WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLocal,
      WebFeature::kAddressSpaceUnknownSecureContextEmbeddedLocal,
      WebFeature::kAddressSpaceUnknownNonSecureContextEmbeddedLocal,
      WebFeature::kAddressSpacePublicSecureContextEmbeddedPrivate,
      WebFeature::kAddressSpacePublicNonSecureContextEmbeddedPrivate,
      WebFeature::kAddressSpaceUnknownSecureContextEmbeddedPrivate,
      WebFeature::kAddressSpaceUnknownNonSecureContextEmbeddedPrivate,
      WebFeature::kAddressSpacePrivateSecureContextNavigatedToLocal,
      WebFeature::kAddressSpacePrivateNonSecureContextNavigatedToLocal,
      WebFeature::kAddressSpacePublicSecureContextNavigatedToLocal,
      WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal,
      WebFeature::kAddressSpaceUnknownSecureContextNavigatedToLocal,
      WebFeature::kAddressSpaceUnknownNonSecureContextNavigatedToLocal,
      WebFeature::kAddressSpacePublicSecureContextNavigatedToPrivate,
      WebFeature::kAddressSpacePublicNonSecureContextNavigatedToPrivate,
      WebFeature::kAddressSpaceUnknownSecureContextNavigatedToPrivate,
      WebFeature::kAddressSpaceUnknownNonSecureContextNavigatedToPrivate,
      WebFeature::kPrivateNetworkAccessIgnoredPreflightError,
      WebFeature::kPrivateNetworkAccessFetchedWorkerScript,
      WebFeature::kPrivateNetworkAccessWithinWorker,
  };
}

// Private Network Access is a web platform specification aimed at securing
// requests made from public websites to the private network and localhost.
//
// It is mostly implemented in content/, but some of its integrations (
// (with Blink UseCounters, with chrome/-specific special schemes) cannot be
// tested in content/, however, thus we define this standalone test here.
//
// See also:
//
//  - specification: https://wicg.github.io/private-network-access.
//  - feature browsertests:
//    //content/browser/renderer_host/private_network_access_browsertest.cc
//
class PrivateNetworkAccessBrowserTestBase : public InProcessBrowserTest {
 public:
  PrivateNetworkAccessBrowserTestBase(
      std::vector<base::Feature> enabled_features,
      std::vector<base::Feature> disabled_features) {
    features_.InitWithFeatures(enabled_features, disabled_features);
  }

  content::WebContents* web_contents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  // Never returns nullptr. The returned server is already Start()ed.
  //
  // NOTE: This is defined as a method on the test fixture instead of a free
  // function because GetChromeTestDataDir() is a test fixture method itself.
  // We return a unique_ptr because EmbeddedTestServer is not movable and C++17
  // support is not available at time of writing.
  std::unique_ptr<net::EmbeddedTestServer> NewServer(
      net::EmbeddedTestServer::Type server_type =
          net::EmbeddedTestServer::TYPE_HTTP) {
    std::unique_ptr<net::EmbeddedTestServer> server =
        std::make_unique<net::EmbeddedTestServer>(server_type);
    server->AddDefaultHandlers(GetChromeTestDataDir());
    EXPECT_TRUE(server->Start());
    return server;
  }

 protected:
  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // The public key used to verify test trial tokens.
    // See: //docs/origin_trial_integration.md
    constexpr char kOriginTrialTestPublicKey[] =
        "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";
    command_line->AppendSwitchASCII(embedder_support::kOriginTrialPublicKey,
                                    kOriginTrialTestPublicKey);
  }

 private:
  base::test::ScopedFeatureList features_;
};

class PrivateNetworkAccessWithFeatureDisabledBrowserTest
    : public PrivateNetworkAccessBrowserTestBase {
 public:
  PrivateNetworkAccessWithFeatureDisabledBrowserTest()
      : PrivateNetworkAccessBrowserTestBase(
            {},
            {
                features::kBlockInsecurePrivateNetworkRequests,
                features::kBlockInsecurePrivateNetworkRequestsFromPrivate,
            }) {}
};

class PrivateNetworkAccessWithFeatureEnabledBrowserTest
    : public PrivateNetworkAccessBrowserTestBase {
 public:
  PrivateNetworkAccessWithFeatureEnabledBrowserTest()
      : PrivateNetworkAccessBrowserTestBase(
            {
                blink::features::kPlzDedicatedWorker,
                features::kBlockInsecurePrivateNetworkRequests,
                features::kBlockInsecurePrivateNetworkRequestsFromPrivate,
                features::kBlockInsecurePrivateNetworkRequestsDeprecationTrial,
                features::kPrivateNetworkAccessSendPreflights,
                features::kPrivateNetworkAccessForWorkers,
                dom_distiller::kReaderMode,
            },
            {}) {}

  void SetUpCommandLine(base::CommandLine* command_line) override {
    PrivateNetworkAccessBrowserTestBase::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kEnableDomDistiller);
  }

 private:
  void SetUpOnMainThread() override {
    PrivateNetworkAccessBrowserTestBase::SetUpOnMainThread();
    // The distiller needs to run in an isolated environment. For tests we
    // can simply use the last value available.
    if (!dom_distiller::DistillerJavaScriptWorldIdIsSet()) {
      dom_distiller::SetDistillerJavaScriptWorldId(
          content::ISOLATED_WORLD_ID_CONTENT_END);
    }
  }
};

class PrivateNetworkAccessRespectPreflightResultsBrowserTest
    : public PrivateNetworkAccessBrowserTestBase {
 public:
  PrivateNetworkAccessRespectPreflightResultsBrowserTest()
      : PrivateNetworkAccessBrowserTestBase(
            {
                blink::features::kPlzDedicatedWorker,
                features::kBlockInsecurePrivateNetworkRequests,
                features::kPrivateNetworkAccessSendPreflights,
                features::kPrivateNetworkAccessRespectPreflightResults,
                features::kPrivateNetworkAccessForWorkers,
            },
            {}) {}
};

// ================
// USECOUNTER TESTS
// ================
//
// UseCounters are translated into UMA histograms at the chrome/ layer, by the
// page_load_metrics component. These tests verify that UseCounters are recorded
// correctly by Private Network Access code in the right circumstances.

// This test verifies that no feature is counted for the initial navigation from
// a new tab to a page served by localhost.
//
// Regression test for https://crbug.com/1134601.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                       DoesNotRecordAddressSpaceFeatureForInitialNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());
}

// This test verifies that no feature is counted for top-level navigations from
// a public page to a local page.
//
// TODO(crbug.com/1129326): Revisit this once the story around top-level
// navigations is closer to being resolved. Counting these events will help
// decide what to do.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                       DoesNotRecordAddressSpaceFeatureForRegularNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
  EXPECT_TRUE(content::NavigateToURL(web_contents(), LocalSecureURL(*server)));

  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());
}

// This test verifies that when a secure context served from the public address
// space loads a resource from the local network, the correct WebFeature is
// use-counted.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                       RecordsAddressSpaceFeatureForFetch) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());

  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    fetch("/defaultresponse").then(response => response.ok)
  )"));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kAddressSpacePublicSecureContextEmbeddedLocal, 1},
      }));
}

// This test verifies that when a preflight request is sent ahead of a private
// network request, fails, and enforcement is not enabled, the correct
// WebFeature is use-counted to reflect the suppressed error.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsAddressSpaceFeatureForFetchWithPreflight) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  // The server does not reply with valid CORS headers, so the preflight fails.
  // The enforcement feature is not enabled however, so the error is suppressed.
  // Instead, a warning is shown in DevTools and a WebFeature use-counted.
  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    fetch("/defaultresponse").then(response => response.ok)
  )"));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kAddressSpacePublicSecureContextEmbeddedLocal, 1},
          {WebFeature::kPrivateNetworkAccessIgnoredPreflightError, 1},
      }));
}

// This test verifies that when a non-secure context served from the public
// address space loads a resource from the local network, the correct WebFeature
// is use-counted.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                       RecordsAddressSpaceFeatureForFetchInNonSecureContext) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());

  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    fetch("/defaultresponse").then(response => response.ok)
  )"));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLocal, 1},
      }));
}

// This test verifies that when the user navigates a `public` document to a
// document served by a non-public IP, no address space feature is recorded.
IN_PROC_BROWSER_TEST_F(
    PrivateNetworkAccessWithFeatureEnabledBrowserTest,
    DoesNotRecordAddressSpaceFeatureForBrowserInitiatedNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), LocalNonSecureURL(*server)));

  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());
}

// This test verifies that when a `public` document navigates itself to a
// document served by a non-public IP, the correct address space feature is
// recorded.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsAddressSpaceFeatureForNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());

  EXPECT_TRUE(content::NavigateToURLFromRenderer(web_contents(),
                                                 LocalNonSecureURL(*server)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1},
      }));
}

// This test verifies that when a `public` document navigates itself to a
// document served by a non-public IP, the correct address space feature is
// recorded, even if the target document carries a CSP `treat-as-public-address`
// directive.
IN_PROC_BROWSER_TEST_F(
    PrivateNetworkAccessWithFeatureEnabledBrowserTest,
    RecordsAddressSpaceFeatureForNavigationToTreatAsPublicAddress) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());

  // Navigate to a different URL with the same CSP directive. If we just tried
  // to navigate to `PublicNonSecureURL(*server)`, nothing would happen.
  EXPECT_TRUE(content::NavigateToURLFromRenderer(
      web_contents(),
      NonSecureURL(
          *server,
          "/set-header?Content-Security-Policy: treat-as-public-address")));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1},
      }));
}

// This test verifies that when a page embeds an empty iframe pointing to
// about:blank, no address space feature is recorded. It serves as a basis for
// comparison with the following tests, which test behavior with iframes.
IN_PROC_BROWSER_TEST_F(
    PrivateNetworkAccessWithFeatureEnabledBrowserTest,
    DoesNotRecordAddressSpaceFeatureForChildAboutBlankNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    new Promise(resolve => {
      const child = document.createElement("iframe");
      child.src = "about:blank";
      child.onload = () => { resolve(true); };
      document.body.appendChild(child);
    })
  )"));

  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());
}

// This test verifies that when a non-secure context served from the public
// address space loads a child frame from the local network, the correct
// WebFeature is use-counted.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsAddressSpaceFeatureForChildNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());

  base::StringPiece script_template = R"(
    new Promise(resolve => {
      const child = document.createElement("iframe");
      child.src = $1;
      child.onload = () => { resolve(true); };
      document.body.appendChild(child);
    })
  )";
  EXPECT_EQ(true,
            content::EvalJs(web_contents(),
                            content::JsReplace(script_template,
                                               LocalNonSecureURL(*server))));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1},
      }));
}

// This test verifies that when a non-secure context served from the public
// address space loads a grand-child frame from the local network, the correct
// WebFeature is use-counted. If inheritance did not work correctly, the
// intermediate about:blank frame might confuse the address space logic.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsAddressSpaceFeatureForGrandchildNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());

  base::StringPiece script_template = R"(
    function addChildFrame(doc, src) {
      return new Promise(resolve => {
        const child = doc.createElement("iframe");
        child.src = src;
        child.onload = () => { resolve(child); };
        doc.body.appendChild(child);
      });
    }

    addChildFrame(document, "about:blank")
      .then(child => addChildFrame(child.contentDocument, $1))
      .then(grandchild =>  true);
  )";
  EXPECT_EQ(true,
            content::EvalJs(web_contents(),
                            content::JsReplace(script_template,
                                               LocalNonSecureURL(*server))));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1},
      }));
}

// This test verifies that the right address space feature is recorded when a
// navigation results in a private network request. Specifically, in this test
// the document being navigated is not the one initiating the navigation (the
// latter being the "remote initiator" referenced by the test name).
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsAddressSpaceFeatureForRemoteInitiatorNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(
      web_contents(),
      NonSecureURL(
          *server,
          "/private_network_access/remote-initiator-navigation.html")));
  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());

  EXPECT_EQ(true, content::EvalJs(web_contents(), content::JsReplace(R"(
    runTest({
      url: "/defaultresponse",
    });
  )")));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocal, 1},
      }));
}

// This test verifies that when the initiator of a navigation is no longer
// around by the time the navigation finishes, then no address space feature is
// recorded, and importantly: the browser does not crash.
IN_PROC_BROWSER_TEST_F(
    PrivateNetworkAccessWithFeatureEnabledBrowserTest,
    DoesNotRecordAddressSpaceFeatureForClosedInitiatorNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(
      web_contents(),
      NonSecureURL(
          *server,
          "/private_network_access/remote-initiator-navigation.html")));

  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    runTest({
      url: new URL("/slow?3", window.location).href,
      initiatorBehavior: "close",
    });
  )"));

  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());
}

// This test verifies that when the initiator of a navigation has already
// navigated itself by the time the navigation finishes, then no address space
// feature is recorded.
IN_PROC_BROWSER_TEST_F(
    PrivateNetworkAccessWithFeatureEnabledBrowserTest,
    DoesNotRecordAddressSpaceFeatureForMissingInitiatorNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(
      web_contents(),
      NonSecureURL(
          *server,
          "/private_network_access/remote-initiator-navigation.html")));

  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    runTest({
      url: new URL("/slow?3", window.location).href,
      initiatorBehavior: "navigate",
    });
  )"));

  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());
}

// This test verifies that private network requests that are blocked are not
// use-counted.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       DoesNotRecordAddressSpaceFeatureForBlockedRequests) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());

  base::HistogramTester base_histogram_tester;

  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    fetch("/defaultresponse").catch(() => true)
  )"));

  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
  base_histogram_tester.ExpectBucketCount(
      "Security.PrivateNetworkAccess.CheckResult",
      network::PrivateNetworkAccessCheckResult::kBlockedByPolicyBlock, 1);

  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());
}

IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsAddressSpaceFeatureForDeprecationTrial) {
  WebFeatureHistogramTester feature_histogram_tester;
  content::DeprecationTrialURLLoaderInterceptor interceptor;

  EXPECT_TRUE(content::NavigateToURL(web_contents(), interceptor.EnabledUrl()));

  EXPECT_EQ(
      feature_histogram_tester.GetCount(
          WebFeature::
              kPrivateNetworkAccessNonSecureContextsAllowedDeprecationTrial),
      1);
}

// This test verifies that resources proxied through a proxy on localhost can
// be fetched from documents in the public IP address space.
// Regression test for https://crbug.com/1253239.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       ProxiedResourcesAllowed) {
  auto server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));

  browser()->profile()->GetPrefs()->Set(
      proxy_config::prefs::kProxy,
      ProxyConfigDictionary::CreateFixedServers(
          server->host_port_pair().ToString(), ""));
  ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
      ->FlushProxyConfigMonitorForTesting();

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    fetch("/defaultresponse").then(response => response.ok)
  )"));

  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());
}

// This test verifies that resources fetched from cache are subject to Private
// Network Access checks. When the fetch is blocked, it is not use-counted.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       DoesNotRecordAddressSpaceFeatureForCachedBlocked) {
  auto server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), LocalNonSecureURL(*server)));

  // Load the resource a first time, to prime the HTTP cache.
  //
  // This caching hinges on the fact that `PublicNonSecureURL(*server)` is
  // same-origin with `LocalNonSecureURL(*server)` (the public one just uses
  // the `Content-Security-Policy: treat-as-public-address` header). Therefore
  // both documents share the same cache key.
  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    fetch("/cachetime").then(response => response.ok)
  )"));

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(false, content::EvalJs(web_contents(), R"(
    fetch("/cachetime").then(response => true).catch(error => false)
  )"));

  EXPECT_THAT(
      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
      IsEmpty());
}

// This test verifies that resources fetched from cache are subject to Private
// Network Access checks. When the fetch is allowed, it is use-counted.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsAddressSpaceFeatureForCachedResource) {
  auto server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), LocalSecureURL(*server)));

  // Load the resource a first time, to prime the HTTP cache.
  //
  // This caching hinges on the fact that `PublicNonSecureURL(*server)` is
  // same-origin with `LocalNonSecureURL(*server)` (the public one just uses
  // the `Content-Security-Policy: treat-as-public-address` header). Therefore
  // both documents share the same cache key.
  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    fetch("/cachetime").then(response => response.ok)
  )"));

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
    fetch("/cachetime").then(response => response.ok)
  )"));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kAddressSpacePublicSecureContextEmbeddedLocal, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a worker script from a non-secure context,
// even when the PNA for workers feature is disabled.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                       RecordsFeatureForWorkerScriptFetchFromNonSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true, content::EvalJs(web_contents(),
                                  FetchWorkerScript(kWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a worker script from a non-secure context,
// and the request fails due to PNA.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsFeatureForWorkerScriptFetchFromNonSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(false, content::EvalJs(web_contents(),
                                   FetchWorkerScript(kWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a worker script from a secure context, even
// when the PNA for workers feature is disabled.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                       RecordsFeatureForWorkerScriptFetchFromSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true, content::EvalJs(web_contents(),
                                  FetchWorkerScript(kWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a worker script from a secure context,
// sends a preflight request, ignores its failure, and loads the script anyway.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsFeatureForWorkerScriptFetchFromSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true, content::EvalJs(web_contents(),
                                  FetchWorkerScript(kWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a worker script from a secure context,
// sends a preflight request, and fails the request due to a preflight error.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessRespectPreflightResultsBrowserTest,
                       RecordsFeatureForWorkerScriptFetchErrorFromSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(false, content::EvalJs(web_contents(),
                                   FetchWorkerScript(kWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a worker script from a secure context,
// sends a preflight request, and succeeds in loading the script.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessRespectPreflightResultsBrowserTest,
                       RecordsFeatureForWorkerScriptFetchSuccessFromSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true, content::EvalJs(
                      web_contents(),
                      FetchWorkerScript(kWorkerScriptWithPnaHeadersPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a shared worker script from a non-secure
// context, even when the PNA for workers feature is disabled.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                       RecordsFeatureForSharedWorkerScriptFetchFromNonSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true,
            content::EvalJs(web_contents(),
                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a shared worker script from a non-secure
// context, and the request fails due to PNA.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsFeatureForSharedWorkerScriptFetchFromNonSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(false,
            content::EvalJs(web_contents(),
                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a shared worker script from a secure context,
// even when the PNA for workers feature is disabled.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
                       RecordsFeatureForSharedWorkerScriptFetchFromSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true,
            content::EvalJs(web_contents(),
                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a shared worker script from a secure context,
// sends a preflight request, ignores its failure, and loads the script anyway.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsFeatureForSharedWorkerScriptFetchFromSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true,
            content::EvalJs(web_contents(),
                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a shared worker script from a secure context,
// sends a preflight request, and fails the request due to a preflight error.
IN_PROC_BROWSER_TEST_F(
    PrivateNetworkAccessRespectPreflightResultsBrowserTest,
    RecordsFeatureForSharedWorkerScriptFetchErrorFromSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(false,
            content::EvalJs(web_contents(),
                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

// This test verifies that a UseCounter is recorded when a document makes a
// private network request to load a shared worker script from a secure context,
// sends a preflight request, and succeeds in loading the script.
IN_PROC_BROWSER_TEST_F(
    PrivateNetworkAccessRespectPreflightResultsBrowserTest,
    RecordsFeatureForSharedWorkerScriptFetchSuccessFromSecure) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  EXPECT_EQ(true, content::EvalJs(web_contents(),
                                  FetchSharedWorkerScript(
                                      kSharedWorkerScriptWithPnaHeadersPath)));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
      }));
}

IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsFeatureForFetchInWorker) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), LocalSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  ASSERT_EQ(true, content::EvalJs(web_contents(), R"(
    (async () => {
      const worker = new Worker("/workers/fetcher_treat_as_public.js");

      const messagePromise = new Promise((resolve) => {
        const listener = (event) => resolve(event.data);
        worker.addEventListener("message", listener, { once: true });
      });

      worker.postMessage("/defaultresponse");

      console.log("Waiting for response.");
      const { error, ok } = await messagePromise;
      if (error !== undefined) {
        throw(error);
      }

      return ok;
    })()
  )"));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessWithinWorker, 1},
      }));
}

IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       RecordsFeatureForFetchInSharedWorker) {
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(web_contents(), LocalSecureURL(*server)));

  WebFeatureHistogramTester feature_histogram_tester;

  ASSERT_EQ(true, content::EvalJs(web_contents(), R"(
    (async () => {
      const worker = await new Promise((resolve, reject) => {
        const worker =
            new SharedWorker("/workers/shared_fetcher_treat_as_public.js");
        worker.port.addEventListener("message", () => resolve(worker));
        worker.addEventListener("error", reject);
        worker.port.start();
      });

      const messagePromise = new Promise((resolve) => {
        const listener = (event) => resolve(event.data);
        worker.port.addEventListener("message", listener, { once: true });
      });

      worker.port.postMessage("/defaultresponse");

      const { error, ok } = await messagePromise;
      if (error !== undefined) {
        throw(error);
      }

      return ok;
    })()
  )"));

  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
      {
          {WebFeature::kPrivateNetworkAccessWithinWorker, 1},
      }));
}

// Test the experimental use counter for accesses to the 0.0.0.0 IP address
// (and the corresponding `[::]` IPv6 address).
//
// In the Internet Protocol Version 4, the address 0.0.0.0 is a non-routable
// meta-address used to designate an invalid, unknown or non-applicable target.
// The real life behavior for 0.0.0.0 is different between operating systems.
// On Windows, it is unreachable, while on MacOS and Linux, 0.0.0.0 means
// all IP addresses on the local machine.
//
// In this case, 0.0.0.0 can be used to access localhost on MacOS and Linux
// and bypass Private Network Access checks, so that we would like to forbid
// fetches to 0.0.0.0. See more: https://crbug.com/1300021
#if BUILDFLAG(IS_WIN)
#define MAYBE_FetchNullIpAddressForNavigation \
  DISABLED_FetchNullIpAddressForNavigation
#else
#define MAYBE_FetchNullIpAddressForNavigation FetchNullIpAddressForNavigation
#endif
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       MAYBE_FetchNullIpAddressForNavigation) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(
      web_contents(), server->GetURL("0.0.0.0", kNoFaviconPath)));

  feature_histogram_tester.ExpectCounts(
      AddFeatureCounts(AllZeroFeatureCounts(AllAddressSpaceFeatures()),
                       {
                           {WebFeature::kPrivateNetworkAccessNullIpAddress, 1},
                       }));
}

#if BUILDFLAG(IS_WIN)
#define MAYBE_FetchNullIpAddressFromDocument \
  DISABLED_FetchNullIpAddressFromDocument
#else
#define MAYBE_FetchNullIpAddressFromDocument FetchNullIpAddressFromDocument
#endif
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       MAYBE_FetchNullIpAddressFromDocument) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), server->GetURL(kNoFaviconPath)));

  auto subresource_url =
      server->GetURL("0.0.0.0", "/set-header?Access-Control-Allow-Origin: *");
  constexpr char kSubresourceScript[] = R"(
    new Promise(resolve => {
      fetch($1).then(e => resolve(true));
    }))";
  EXPECT_EQ(true, content::EvalJs(
                      web_contents(),
                      content::JsReplace(kSubresourceScript, subresource_url)));

  feature_histogram_tester.ExpectCounts(
      AddFeatureCounts(AllZeroFeatureCounts(AllAddressSpaceFeatures()),
                       {
                           {WebFeature::kPrivateNetworkAccessNullIpAddress, 1},
                       }));
}

#if BUILDFLAG(IS_WIN)
#define MAYBE_FetchNullIpAddressFromWorker DISABLED_FetchNullIpAddressFromWorker
#else
#define MAYBE_FetchNullIpAddressFromWorker FetchNullIpAddressFromWorker
#endif
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       MAYBE_FetchNullIpAddressFromWorker) {
  WebFeatureHistogramTester feature_histogram_tester;
  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();

  EXPECT_TRUE(content::NavigateToURL(
      web_contents(), server->GetURL("/workers/fetch_from_worker.html")));

  constexpr char kWorkerScript[] = R"(
    new Promise(resolve => {
      fetch_from_worker($1);
      resolve(true);
    }))";
  auto worker_url =
      server->GetURL("0.0.0.0", "/set-header?Access-Control-Allow-Origin: *");
  EXPECT_EQ(true,
            content::EvalJs(web_contents(),
                            content::JsReplace(kWorkerScript, worker_url)));

  feature_histogram_tester.ExpectCounts(
      AddFeatureCounts(AllZeroFeatureCounts(AllAddressSpaceFeatures()),
                       {
                           {WebFeature::kPrivateNetworkAccessNullIpAddress, 1},
                       }));
}

// ====================
// SPECIAL SCHEME TESTS
// ====================
//
// These tests verify the IP address space assigned to documents loaded from a
// variety of special URL schemes. Since these are not loaded over the network,
// an IP address space must be made up for them.

// This test verifies that the chrome-untrusted:// scheme is considered local
// for the purpose of Private Network Access computations.
// TODO(crbug.com/1244544): The NTP no longer loads a chrome-untrusted://
// iframe in all cases. Find another way to test the chrome-untrusted:// scheme.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       DISABLED_SpecialSchemeChromeUntrusted) {
  // The only way to have a page with a loaded chrome-untrusted:// url without
  // relying on platform specific or components features, is to use the
  // new-tab-page host. chrome-untrusted://new-tab-page is restricted to iframes
  // however so we load chrome://new-tab-page that embeds chrome-untrusted://
  // frame(s) by default.
  EXPECT_TRUE(
      content::NavigateToURL(web_contents(), GURL("chrome://new-tab-page")));
  content::RenderFrameHost* iframe = ChildFrameAt(web_contents(), 0);
  ASSERT_TRUE(iframe);
  EXPECT_TRUE(iframe->GetLastCommittedURL().SchemeIs(
      content::kChromeUIUntrustedScheme));

  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
  GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);

  // TODO(crbug.com/591068): The chrome-untrusted:// page should be kLocal, and
  // not require a Private Network Access CORS preflight. However we have not
  // yet implemented the CORS preflight mechanism, and fixing the underlying
  // issue will not change the test result. Once CORS preflight is implemented,
  // review this test and delete this comment.
  // Note: CSP is blocking javascript eval, unless we run it in an isolated
  // world.
  EXPECT_EQ(true, content::EvalJs(iframe, FetchScript(fetch_url),
                                  content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                                  content::ISOLATED_WORLD_ID_CONTENT_END));
}

// This test verifies that the devtools:// scheme is considered local for the
// purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       SpecialSchemeDevtools) {
  EXPECT_TRUE(content::NavigateToURL(
      web_contents(), GURL("devtools://devtools/bundled/devtools_app.html")));
  EXPECT_TRUE(
      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
          content::kChromeDevToolsScheme));

  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
  GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);

  // TODO(crbug.com/591068): The devtools:// page should be kLocal, and not
  // require a Private Network Access CORS preflight. However we have not yet
  // implemented the CORS preflight mechanism, and fixing the underlying issue
  // will not change the test result. Once CORS preflight is implemented, review
  // this test and delete this comment.
  EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url)));
}

// This test verifies that the chrome-search:// scheme is considered local for
// the purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       SpecialSchemeChromeSearch) {
  EXPECT_TRUE(content::NavigateToURL(
      web_contents(), GURL("chrome-search://most-visited/title.html")));
  ASSERT_TRUE(
      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
          chrome::kChromeSearchScheme));

  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
  GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);

  // TODO(crbug.com/591068): The chrome-search:// page should be kLocal, and not
  // require a Private Network Access CORS preflight. However we have not yet
  // implemented the CORS preflight mechanism, and fixing the underlying issue
  // will not change the test result. Once CORS preflight is implemented, review
  // this test and delete this comment.
  // Note: CSP is blocking javascript eval, unless we run it in an isolated
  // world.
  EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url),
                                  content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                                  content::ISOLATED_WORLD_ID_CONTENT_END));
}

// This test verifies that the chrome-extension:// scheme is considered local
// for the purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       SpecialSchemeChromeExtension) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  extensions::ScopedInstallVerifierBypassForTest install_verifier_bypass;

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  static constexpr char kPageFile[] = "page.html";

  base::Value::List resources;
  resources.Append(kPageFile);
  constexpr char kContents[] = R"(
  <html>
    <head>
      <title>IPAddressSpace of chrome-extension:// schemes.</title>
    </head>
    <body>
    </body>
  </html>
  )";
  base::WriteFile(temp_dir.GetPath().AppendASCII(kPageFile), kContents,
                  sizeof(kContents) - 1);

  extensions::ExtensionBuilder builder("test");
  builder.SetPath(temp_dir.GetPath())
      .SetVersion("1.0")
      .SetLocation(extensions::mojom::ManifestLocation::kExternalPolicyDownload)
      .SetManifestKey("web_accessible_resources", std::move(resources));

  extensions::ExtensionService* service =
      extensions::ExtensionSystem::Get(browser()->profile())
          ->extension_service();
  scoped_refptr<const extensions::Extension> extension = builder.Build();
  service->OnExtensionInstalled(extension.get(), syncer::StringOrdinal(), 0);

  const GURL url = extension->GetResourceURL(kPageFile);

  EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
  ASSERT_TRUE(
      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
          extensions::kExtensionScheme));

  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
  GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);

  // TODO(crbug.com/591068): The chrome-extension:// page should be kLocal, and
  // not require a Private Network Access CORS preflight. However we have not
  // yet implemented the CORS preflight mechanism, and fixing the underlying
  // issue will not change the test result. Once CORS preflight is implemented,
  // review this test and delete this comment.
  // Note: CSP is blocking javascript eval, unless we run it in an isolated
  // world.
  EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url),
                                  content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                                  content::ISOLATED_WORLD_ID_CONTENT_END));
}

// This test verifies that the chrome-distiller:// scheme is considered public
// for the purpose of Private Network Access.
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
                       SpecialSchemeChromeDistiller) {
  // Load the base page to be distilled. Note that HTTPS has to be used
  // otherwise the page won't be distillable.
  std::unique_ptr<net::EmbeddedTestServer> https_server =
      NewServer(net::EmbeddedTestServer::TYPE_HTTPS);
  GURL article_url = https_server->GetURL("/dom_distiller/simple_article.html");

  dom_distiller::TestDistillabilityObserver distillability_observer(
      web_contents());
  dom_distiller::DistillabilityResult expected_result;
  expected_result.is_distillable = true;
  expected_result.is_last = false;
  expected_result.is_mobile_friendly = false;

  EXPECT_TRUE(content::NavigateToURL(web_contents(), article_url));
  // This blocks until the page is found to be distillable.
  distillability_observer.WaitForResult(expected_result);

  // Distill the page. It will be placed in a new WebContents replacing the old
  // one.
  DistillCurrentPageAndView(web_contents());
  dom_distiller::DistilledPageObserver(web_contents())
      .WaitUntilFinishedLoading();

  EXPECT_TRUE(
      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
          dom_distiller::kDomDistillerScheme));

  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
  GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);

  // Note: CSP is blocking javascript eval, unless we run it in an isolated
  // world.
  EXPECT_EQ(false, content::EvalJs(web_contents(), FetchScript(fetch_url),
                                   content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
                                   content::ISOLATED_WORLD_ID_CONTENT_END));
}

// =================
// AUTO-RELOAD TESTS
// =================

// Intercepts the first load to a URL and fails the request with an error.
class NetErrorInterceptor final {
 public:
  NetErrorInterceptor(GURL url, net::Error error)
      : url_(std::move(url)),
        error_(error),
        interceptor_(base::BindRepeating(&NetErrorInterceptor::Intercept,
                                         base::Unretained(this))) {}

  ~NetErrorInterceptor() = default;

  // Instances of this type are neither copyable nor movable.
  NetErrorInterceptor(const NetErrorInterceptor&) = delete;
  NetErrorInterceptor& operator=(const NetErrorInterceptor&) = delete;

 private:
  bool Intercept(content::URLLoaderInterceptor::RequestParams* params) {
    const GURL& request_url = params->url_request.url;
    if (request_url != url_ || did_intercept_) {
      return false;
    }

    did_intercept_ = true;

    network::URLLoaderCompletionStatus status;
    status.error_code = error_;
    params->client->OnComplete(status);
    return true;
  }

  const GURL url_;
  const net::Error error_;

  // Whether this instance already intercepted and failed a request.
  bool did_intercept_ = false;

  // Interceptor must be declared after all state used in `Intercept()`, to
  // avoid use-after-free at destruction time.
  const content::URLLoaderInterceptor interceptor_;
};

class PrivateNetworkAccessAutoReloadBrowserTest
    : public PrivateNetworkAccessBrowserTestBase {
 public:
  PrivateNetworkAccessAutoReloadBrowserTest()
      : PrivateNetworkAccessBrowserTestBase(
            {
                features::kBlockInsecurePrivateNetworkRequests,
                features::kBlockInsecurePrivateNetworkRequestsForNavigations,
                features::kBlockInsecurePrivateNetworkRequestsDeprecationTrial,
            },
            {}) {}

  void SetUpOnMainThread() override {
    PrivateNetworkAccessBrowserTestBase::SetUpOnMainThread();

    error_page::NetErrorAutoReloader::CreateForWebContents(web_contents());
  }
};

// This test verifies that when a document in the `local` address space fails to
// load due to a transient network error, it is auto-reloaded a short while
// later and that fetch is not blocked as a private network request.
//
// TODO(crbug.com/1326341): Flaky on Linux MSan.
#if BUILDFLAG(IS_LINUX) && defined(MEMORY_SANITIZER)
#define MAYBE_AutoReloadWorks DISABLED_AutoReloadWorks
#else
#define MAYBE_AutoReloadWorks AutoReloadWorks
#endif
IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessAutoReloadBrowserTest,
                       MAYBE_AutoReloadWorks) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL url = embedded_test_server()->GetURL("/defaultresponse");

  // There should be two navigations in total: one failed, one successful.
  content::TestNavigationObserver observer(web_contents(), 2);

  // This interceptor will only fail the first request to `url`.
  NetErrorInterceptor interceptor(url, net::ERR_UNEXPECTED);

  EXPECT_FALSE(content::NavigateToURL(web_contents(), url));

  // Observe second navigation, which succeeds.
  observer.Wait();
  EXPECT_TRUE(observer.last_navigation_succeeded());
}

}  // namespace


text:
package com.example.android.sunshine.app;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.TextView;

import com.example.android.sunshine.app.data.WeatherContract;
import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;

/**
 * Encapsulates fetching the forecast and displaying it as a {@link android.support.v7.widget.RecyclerView} layout.
 */
public class ForecastFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>, SharedPreferences.OnSharedPreferenceChangeListener {
	public static final String LOG_TAG = ForecastFragment.class.getSimpleName();
	private static final String TAG = ForecastFragment.class.getSimpleName();
	private ForecastAdapter mForecastAdapter;
	private RecyclerView mRecyclerView;
	private boolean mUseTodayLayout, mAutoSelectView;
	private int mChoiceMode;
	private boolean mHoldForTransition;
	private long mInitialSelectedDate = -1;

	private static final String SELECTED_KEY = "selected_position";

	private static final int FORECAST_LOADER = 0;
	// For the forecast view we're showing only a small subset of the stored data.
	// Specify the columns we need.
	public static final String[] FORECAST_COLUMNS = {
			// In this case the id needs to be fully qualified with a table name, since
			// the content provider joins the location & weather tables in the background
			// (both have an _id column)
			// On the one hand, that's annoying.  On the other, you can search the weather table
			// using the location set by the user, which is only in the Location table.
			// So the convenience is worth it.
			WeatherContract.WeatherEntry.TABLE_NAME + "." + WeatherContract.WeatherEntry._ID,
			WeatherContract.WeatherEntry.COLUMN_DATE,
			WeatherContract.WeatherEntry.COLUMN_SHORT_DESC,
			WeatherContract.WeatherEntry.COLUMN_MAX_TEMP,
			WeatherContract.WeatherEntry.COLUMN_MIN_TEMP,
			WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING,
			WeatherContract.WeatherEntry.COLUMN_WEATHER_ID,
			WeatherContract.LocationEntry.COLUMN_COORD_LAT,
			WeatherContract.LocationEntry.COLUMN_COORD_LONG
	};

	// These indices are tied to FORECAST_COLUMNS.  If FORECAST_COLUMNS changes, these
	// must change.
	static final int COL_WEATHER_ID = 0;
	static final int COL_WEATHER_DATE = 1;
	static final int COL_WEATHER_DESC = 2;
	static final int COL_WEATHER_MAX_TEMP = 3;
	static final int COL_WEATHER_MIN_TEMP = 4;
	static final int COL_LOCATION_SETTING = 5;
	static final int COL_WEATHER_CONDITION_ID = 6;
	static final int COL_COORD_LAT = 7;
	static final int COL_COORD_LONG = 8;

	/**
	 * A callback interface that all activities containing this fragment must
	 * implement. This mechanism allows activities to be notified of item
	 * selections.
	 */
	public interface Callback {
		/**
		 * DetailFragmentCallback for when an item has been selected.
		 */
		public void onItemSelected(Uri dateUri, ForecastAdapter.ForecastAdapterViewHolder vh);
	}

	public ForecastFragment() {
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// Add this line in order for this fragment to handle menu events.
		setHasOptionsMenu(true);

	}

	@Override
	public void onResume() {
		SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
		sp.registerOnSharedPreferenceChangeListener(this);
		super.onResume();
	}

	@Override
	public void onPause() {
		SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
		sp.unregisterOnSharedPreferenceChangeListener(this);
		super.onPause();
	}

	@Override
	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
		inflater.inflate(R.menu.forecastfragment, menu);
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
//        if (id == R.id.action_refresh) {
//            updateWeather();
//            return true;
//        }
		if (id == R.id.action_map) {
			openPreferredLocationInMap();
			return true;
		}

		return super.onOptionsItemSelected(item);
	}

	@Override
	public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
		super.onInflate(activity, attrs, savedInstanceState);
		TypedArray a = activity.obtainStyledAttributes(attrs, R.styleable.ForecastFragment,
				0, 0);
		mChoiceMode = a.getInt(R.styleable.ForecastFragment_android_choiceMode, AbsListView.CHOICE_MODE_NONE);
		mAutoSelectView = a.getBoolean(R.styleable.ForecastFragment_autoSelectView, false);
		mHoldForTransition = a.getBoolean(R.styleable.ForecastFragment_sharedElementTransitions, false);
		a.recycle();
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
							 Bundle savedInstanceState) {

		View rootView = inflater.inflate(R.layout.fragment_main, container, false);

		// Get a reference to the RecyclerView, and attach this adapter to it.
		mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview_forecast);

		// Set the layout manager
		mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
		View emptyView = rootView.findViewById(R.id.recyclerview_forecast_empty);

		// use this setting to improve performance if you know that changes
		// in content do not change the layout size of the RecyclerView
		mRecyclerView.setHasFixedSize(true);

		// The ForecastAdapter will take data from a source and
		// use it to populate the RecyclerView it's attached to.
		mForecastAdapter = new ForecastAdapter(getActivity(), new ForecastAdapter.ForecastAdapterOnClickHandler() {
			@Override
			public void onClick(Long date, ForecastAdapter.ForecastAdapterViewHolder vh) {
				String locationSetting = Utility.getPreferredLocation(getActivity());
				((Callback) getActivity())
						.onItemSelected(WeatherContract.WeatherEntry.buildWeatherLocationWithDate(
								locationSetting, date),
								vh
						);
			}
		}, emptyView, mChoiceMode);

		// specify an adapter (see also next example)
		mRecyclerView.setAdapter(mForecastAdapter);

		final View parallaxView = rootView.findViewById(R.id.parallax_bar);
		if (null != parallaxView) {
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
				mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
					@TargetApi(Build.VERSION_CODES.HONEYCOMB)
					@Override
					public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
						super.onScrolled(recyclerView, dx, dy);
						int max = parallaxView.getHeight();
						if (dy > 0) {
							parallaxView.setTranslationY(Math.max(-max, parallaxView.getTranslationY() - dy / 2));
						} else {
							parallaxView.setTranslationY(Math.min(0, parallaxView.getTranslationY() - dy / 2));
						}
					}
				});
			}
		}

		final AppBarLayout appbarView = (AppBarLayout) rootView.findViewById(R.id.appbar);
		if (null != appbarView) {
			ViewCompat.setElevation(appbarView, 0);
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
				mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
					@TargetApi(Build.VERSION_CODES.LOLLIPOP)
					@Override
					public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
						if (0 == mRecyclerView.computeVerticalScrollOffset()) {
							appbarView.setElevation(0);
						} else {
							appbarView.setElevation(appbarView.getTargetElevation());
						}
					}
				});
			}
		}

		// If there's instance state, mine it for useful information.
		// The end-goal here is that the user never knows that turning their device sideways
		// does crazy lifecycle related things.  It should feel like some stuff stretched out,
		// or magically appeared to take advantage of room, but data or place in the app was never
		// actually *lost*.
		if (savedInstanceState != null) {
			mForecastAdapter.onRestoreInstanceState(savedInstanceState);
		}

		mForecastAdapter.setUseTodayLayout(mUseTodayLayout);

		return rootView;
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		// We hold for transition here just in-case the activity
		// needs to be re-created. In a standard return transition,
		// this doesn't actually make a difference.
		if (mHoldForTransition) {
			getActivity().supportPostponeEnterTransition();
		}
		getLoaderManager().initLoader(FORECAST_LOADER, null, this);
		super.onActivityCreated(savedInstanceState);
	}

	// since we read the location when we create the loader, all we need to do is restart things
	void onLocationChanged() {
		getLoaderManager().restartLoader(FORECAST_LOADER, null, this);
	}

	private void openPreferredLocationInMap() {
		// Using the URI scheme for showing a location found on a map.  This super-handy
		// intent can is detailed in the "Common Intents" page of Android's developer site:
		// http://developer.android.com/guide/components/intents-common.html#Maps
		if (null != mForecastAdapter) {
			Cursor c = mForecastAdapter.getCursor();
			if (null != c) {
				c.moveToPosition(0);
				String posLat = c.getString(COL_COORD_LAT);
				String posLong = c.getString(COL_COORD_LONG);
				Uri geoLocation = Uri.parse("geo:" + posLat + "," + posLong);

				Intent intent = new Intent(Intent.ACTION_VIEW);
				intent.setData(geoLocation);

				if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
					startActivity(intent);
				} else {
					Log.d(LOG_TAG, "Couldn't call " + geoLocation.toString() + ", no receiving apps installed!");
				}
			}

		}
	}

	@Override
	public void onSaveInstanceState(Bundle outState) {
		// When tablets rotate, the currently selected list item needs to be saved.
		mForecastAdapter.onSaveInstanceState(outState);
		super.onSaveInstanceState(outState);
	}

	@Override
	public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
		// This is called when a new Loader needs to be created.  This
		// fragment only uses one loader, so we don't care about checking the id.

		// To only show current and future dates, filter the query to return weather only for
		// dates after or including today.

		// Sort order:  Ascending, by date.
		String sortOrder = WeatherContract.WeatherEntry.COLUMN_DATE + " ASC";

		String locationSetting = Utility.getPreferredLocation(getActivity());
		Uri weatherForLocationUri = WeatherContract.WeatherEntry.buildWeatherLocationWithStartDate(
				locationSetting, System.currentTimeMillis());

		return new CursorLoader(getActivity(),
				weatherForLocationUri,
				FORECAST_COLUMNS,
				null,
				null,
				sortOrder);
	}

	@Override
	public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
		mForecastAdapter.swapCursor(data);
		updateEmptyView();
		if (data.getCount() == 0) {
			getActivity().supportStartPostponedEnterTransition();
		} else {
			mRecyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
				@Override
				public boolean onPreDraw() {
					// Since we know we're going to get items, we keep the listener around until
					// we see Children.
					if (mRecyclerView.getChildCount() > 0) {
						mRecyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
						int position = mForecastAdapter.getSelectedItemPosition();
						if (position == RecyclerView.NO_POSITION &&
								-1 != mInitialSelectedDate) {
							Cursor data = mForecastAdapter.getCursor();
							int count = data.getCount();
							int dateColumn = data.getColumnIndex(WeatherContract.WeatherEntry.COLUMN_DATE);
							for (int i = 0; i < count; i++) {
								data.moveToPosition(i);
								if (data.getLong(dateColumn) == mInitialSelectedDate) {
									position = i;
									break;
								}
							}
						}
						if (position == RecyclerView.NO_POSITION) position = 0;
						// If we don't need to restart the loader, and there's a desired position to restore
						// to, do so now.
						mRecyclerView.smoothScrollToPosition(position);
						RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(position);
						if (null != vh && mAutoSelectView) {
							mForecastAdapter.selectView(vh);
						}
						if (mHoldForTransition) {
							getActivity().supportStartPostponedEnterTransition();
						}
						return true;
					}
					return false;
				}
			});
		}

	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		if (null != mRecyclerView) {
			mRecyclerView.clearOnScrollListeners();
		}
	}

	@Override
	public void onLoaderReset(Loader<Cursor> loader) {
		mForecastAdapter.swapCursor(null);
	}

	public void setUseTodayLayout(boolean useTodayLayout) {
		mUseTodayLayout = useTodayLayout;
		if (mForecastAdapter != null) {
			mForecastAdapter.setUseTodayLayout(mUseTodayLayout);
		}
	}

	public void setInitialSelectedDate(long initialSelectedDate) {
		mInitialSelectedDate = initialSelectedDate;
	}

	/*
		Updates the empty list view with contextually relevant information that the user can
		use to determine why they aren't seeing weather.
	 */
	private void updateEmptyView() {
		if (mForecastAdapter.getItemCount() == 0) {
			TextView tv = (TextView) getView().findViewById(R.id.recyclerview_forecast_empty);
			if (null != tv) {
				// if cursor is empty, why? do we have an invalid location
				int message = R.string.empty_forecast_list;
				@SunshineSyncAdapter.LocationStatus int location = Utility.getLocationStatus(getActivity());
				switch (location) {
					case SunshineSyncAdapter.LOCATION_STATUS_SERVER_DOWN:
						message = R.string.empty_forecast_list_server_down;
						break;
					case SunshineSyncAdapter.LOCATION_STATUS_SERVER_INVALID:
						message = R.string.empty_forecast_list_server_error;
						break;
					case SunshineSyncAdapter.LOCATION_STATUS_INVALID:
						message = R.string.empty_forecast_list_invalid_location;
						break;
					default:
						if (!Utility.isNetworkAvailable(getActivity())) {
							message = R.string.empty_forecast_list_no_network;
						}
				}
				tv.setText(message);
			}
		}
	}

	@Override
	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
		if (key.equals(getString(R.string.pref_location_status_key))) {
			updateEmptyView();
		}
	}
}

redpajama_set_name: RedPajamaGithub


text:
$packerWindowsDir = 'C:\Windows\packer'
New-Item -Path $packerWindowsDir -ItemType Directory -Force

# final shutdown command
$shutdownCmd = @"
netsh advfirewall firewall set rule name="WinRM-HTTP" new action=block

C:/windows/system32/sysprep/sysprep.exe /generalize /oobe /unattend:C:/Windows/packer/unattended.xml /quiet /shutdown
"@

# unattend XML to run on first boot after sysprep
$unattendedXML = @"
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="generalize">
        <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <SkipRearm>1</SkipRearm>
        </component>
        <component name="Microsoft-Windows-PnpSysprep" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <PersistAllDeviceInstalls>false</PersistAllDeviceInstalls>
            <DoNotCleanUpNonPresentDevices>false</DoNotCleanUpNonPresentDevices>
        </component>
    </settings>
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <InputLocale>de-DE</InputLocale>
            <SystemLocale>de-DE</SystemLocale>
            <UILanguage>en-US</UILanguage>
            <UserLocale>de-DE</UserLocale>
        </component>
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
                <ProtectYourPC>1</ProtectYourPC>
                <NetworkLocation>Home</NetworkLocation>
                <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
            </OOBE>
            <TimeZone>W. Europe Standard Time</TimeZone>
            <UserAccounts>
                <AdministratorPassword>
                    <Value>vagrant</Value>
                    <PlainText>true</PlainText>
                </AdministratorPassword>
                <LocalAccounts>
                    <LocalAccount wcm:action="add">
                        <Password>
                            <Value>vagrant</Value>
                            <PlainText>true</PlainText>
                        </Password>
                        <Group>administrators</Group>
                        <DisplayName>Vagrant</DisplayName>
                        <Name>vagrant</Name>
                        <Description>Vagrant User</Description>
                    </LocalAccount>
                </LocalAccounts>
            </UserAccounts>
        </component>
    </settings>
    <settings pass="specialize">
    </settings>
</unattend>
"@

Set-Content -Path "$($packerWindowsDir)\PackerShutdown.bat" -Value $shutdownCmd
Set-Content -Path "$($packerWindowsDir)\unattended.xml" -Value $unattendedXML

# will run on first boot
# https://technet.microsoft.com/en-us/library/cc766314(v=ws.10).aspx
$setupComplete = @"
netsh advfirewall firewall set rule name="WinRM-HTTP" new action=allow
"@

New-Item -Path 'C:\Windows\Setup\Scripts' -ItemType Directory -Force
Set-Content -path "C:\Windows\Setup\Scripts\SetupComplete.cmd" -Value $setupComplete


text:
package fucksocks.server.filters;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;

import fucksocks.common.IP;
import fucksocks.common.IPRange;
import fucksocks.server.Session;

/**
 *
 * The class IPSessionFilter represents
 *
 * @author Youchao Feng
 * @date May 2, 2015 12:42:18 AM
 * @version 1.0
 *
 */
public class IPSessionFilter implements SessionFilter {

  /**
   * Ranges of IP address.
   */
  private List<IPRange> ipRanges = new ArrayList<IPRange>();

  /**
   * Mode.
   */
  private Mode mode = Mode.WHITE_LIST;

  @Override
  public boolean doFilter(Session session) {
    IP ip = new IP(((InetSocketAddress) session.getRemoteAddress()).getAddress().getAddress());
    switch (mode) {

      case BLACK_LIST:
        for (IPRange range : ipRanges) {
          if (range.contains(ip)) {
            return false;
          }
        }
        return true;

      case WHITE_LIST:
        for (IPRange range : ipRanges) {
          if (range.contains(ip)) {
            return true;
          }
        }
        return false;

      default:
        break;

    }
    return false;
  }

  /**
   * Returns all IP address ranges.
   *
   * @return All IP address ranges.
   */
  public List<IPRange> getIPRanges() {
    return ipRanges;
  }

  /**
   * Sets IP address ranges.
   *
   * @param ipRanges IP address range.
   */
  public void setIPRanges(List<IPRange> ipRanges) {
    this.ipRanges = ipRanges;
  }

  /**
   * Returns mode.
   *
   * @return mode.
   */
  public Mode getMode() {
    return mode;
  }

  /**
   * Changes mode.
   *
   * @param mode Mode.
   * @return The instance of {@link IPSessionFilter}.
   */
  public IPSessionFilter setMode(Mode mode) {
    this.mode = mode;
    return this;
  }

  /**
   * Add an IP address range to the List of IP address ranges.
   *
   * @param ipRange IP address range in String such as "192.168.1.1-192.168.255.255".
   * @return The instance of {@link IPSessionFilter}.
   */
  public IPSessionFilter addIpRange(String ipRange) {
    ipRanges.add(IPRange.parseFromString(ipRange));
    return this;
  }

  /**
   * Add an IP address to the list of IP address ranges.
   *
   * @param ip IP address in String such as "192.168.1.1".
   * @return The instance of {@link IPSessionFilter}.
   */
  public IPSessionFilter addIp(String ip) {
    ipRanges.add(IPRange.parseFromString(ip + "-" + ip));
    return this;
  }

  /**
   * The enumeration Mode represents a Mode of {@link IPSessionFilter}.
   *
   * @author Youchao Feng
   * @date May 4, 2015 3:44:37 AM
   * @version 1.0
   *
   */
  public enum Mode {
    /**
     * Black list mode.
     */
    BLACK_LIST,
    /**
     * White list mode.
     */
    WHITE_LIST
  }

}


text:
package com.microsoft.azure.management.mediaservices.v2018_07_01;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;

/**
 * Describes the properties for generating an MPEG-2 Transport Stream (ISO/IEC
 * 13818-1) output video file(s).
 */
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@odata\\.type", defaultImpl = TransportStreamFormat.class)
@JsonTypeName("#Microsoft.Media.TransportStreamFormat")
public class TransportStreamFormat extends MultiBitrateFormat {
}