Browse Source

chore: cherry-pick dbde8795233a from chromium (#32211)

* chore: cherry-pick dbde8795233a from chromium

* chore: update patches

Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
Co-authored-by: Electron Bot <[email protected]>
Pedro Pontes 3 years ago
parent
commit
67a565310a
2 changed files with 309 additions and 0 deletions
  1. 1 0
      patches/chromium/.patches
  2. 308 0
      patches/chromium/cherry-pick-dbde8795233a.patch

+ 1 - 0
patches/chromium/.patches

@@ -127,6 +127,7 @@ use_weakptrs_for_the_threadediconloader_s_background_tasks.patch
 cherry-pick-a5f54612590d.patch
 mas_gate_private_enterprise_APIs.patch
 fix_aspect_ratio_with_max_size.patch
+cherry-pick-dbde8795233a.patch
 cherry-pick-da11d71a0227.patch
 m96_fileapi_move_origin_checks_in_bloburlstore_sooner.patch
 cherry-pick-6bb320d134b1.patch

+ 308 - 0
patches/chromium/cherry-pick-dbde8795233a.patch

@@ -0,0 +1,308 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Patrick Meenan <[email protected]>
+Date: Fri, 3 Dec 2021 18:15:17 +0000
+Subject: Prevent opaque range request responses from entering the preload
+ cache
+
+ResourceLoader cancels range request responses that were not initiated
+with range request headers causing them to error out and be cleared from
+the preload cache. Other responses (200, 416, error, etc) complete
+successfully and would otherwise enter the preload cache, making them
+observable.
+
+This prevents opaque range responses of any kind from persisting in the
+preload cache (which would not naturally have any anyway).
+
+(cherry picked from commit a5f630e5f94da28a926d60da7dde194acd8697f0)
+
+Bug: 1270990
+Change-Id: Ife9922fe0b88e39722f3664ddd091a1516892157
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3294001
+Reviewed-by: Ben Kelly <[email protected]>
+Reviewed-by: Yoav Weiss <[email protected]>
+Commit-Queue: Patrick Meenan <[email protected]>
+Cr-Original-Commit-Position: refs/heads/main@{#946055}
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3313416
+Auto-Submit: Patrick Meenan <[email protected]>
+Bot-Commit: Rubber Stamper <[email protected]>
+Cr-Commit-Position: refs/branch-heads/4664@{#1222}
+Cr-Branched-From: 24dc4ee75e01a29d390d43c9c264372a169273a7-refs/heads/main@{#929512}
+
+diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+index 8fc72ba0b932bd20b96630373e3bc4ee7765c8a1..d27c52259038fe5daf23f9ad0acbf7cb1ade38fb 100644
+--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
++++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+@@ -1873,6 +1873,19 @@ void ResourceFetcher::HandleLoaderFinish(Resource* resource,
+   }
+ 
+   resource->VirtualTimePauser().UnpauseVirtualTime();
++
++  // A response should not serve partial content if it was not requested via a
++  // Range header: https://fetch.spec.whatwg.org/#main-fetch so keep it out
++  // of the preload cache in case of a non-206 response (which generates an
++  // error).
++  if (resource->GetResponse().GetType() ==
++          network::mojom::FetchResponseType::kOpaque &&
++      resource->GetResponse().HasRangeRequested() &&
++      !resource->GetResourceRequest().HttpHeaderFields().Contains(
++          net::HttpRequestHeaders::kRange)) {
++    RemovePreload(resource);
++  }
++
+   if (type == kDidFinishLoading) {
+     resource->Finish(response_end, freezable_task_runner_.get());
+ 
+diff --git a/third_party/blink/web_tests/external/wpt/fetch/range/resources/partial-text.py b/third_party/blink/web_tests/external/wpt/fetch/range/resources/partial-text.py
+new file mode 100644
+index 0000000000000000000000000000000000000000..a0058551d52d45b3c16882014be740d75e51ddd1
+--- /dev/null
++++ b/third_party/blink/web_tests/external/wpt/fetch/range/resources/partial-text.py
+@@ -0,0 +1,47 @@
++"""
++This generates a partial response for a 100-byte text file.
++"""
++import re
++
++from wptserve.utils import isomorphic_decode
++
++def main(request, response):
++    total_length = int(request.GET.first(b'length', b'100'))
++    partial_code = int(request.GET.first(b'partial', b'206'))
++    range_header = request.headers.get(b'Range', b'')
++
++    # Send a 200 if there is no range request
++    if not range_header:
++        to_send = ''.zfill(total_length)
++        response.headers.set(b"Content-Type", b"text/plain")
++        response.headers.set(b"Cache-Control", b"no-cache")
++        response.headers.set(b"Content-Length", total_length)
++        response.content = to_send
++        return
++
++    # Simple range parsing, requires specifically "bytes=xxx-xxxx"
++    range_header_match = re.search(r'^bytes=(\d*)-(\d*)$', isomorphic_decode(range_header))
++    start, end = range_header_match.groups()
++    start = int(start)
++    end = int(end) if end else total_length
++    length = end - start
++
++    # Error the request if the range goes beyond the length
++    if length <= 0 or end > total_length:
++        response.set_error(416, u"Range Not Satisfiable")
++        response.write()
++        return
++
++    # Generate a partial response of the requested length
++    to_send = ''.zfill(length)
++    response.headers.set(b"Content-Type", b"text/plain")
++    response.headers.set(b"Accept-Ranges", b"bytes")
++    response.headers.set(b"Cache-Control", b"no-cache")
++    response.status = partial_code
++
++    content_range = b"bytes %d-%d/%d" % (start, end, total_length)
++
++    response.headers.set(b"Content-Range", content_range)
++    response.headers.set(b"Content-Length", length)
++
++    response.content = to_send
+diff --git a/third_party/blink/web_tests/external/wpt/fetch/range/resources/range-sw.js b/third_party/blink/web_tests/external/wpt/fetch/range/resources/range-sw.js
+index 3680c0c471d3d5f36c4aba4cc58dcd52c38a08df..b47823f03b4ef3749e622fbf7dd3b515a216b5be 100644
+--- a/third_party/blink/web_tests/external/wpt/fetch/range/resources/range-sw.js
++++ b/third_party/blink/web_tests/external/wpt/fetch/range/resources/range-sw.js
+@@ -12,7 +12,7 @@ async function broadcast(msg) {
+   }
+ }
+ 
+-addEventListener('fetch', event => {
++addEventListener('fetch', async event => {
+   /** @type Request */
+   const request = event.request;
+   const url = new URL(request.url);
+@@ -34,6 +34,11 @@ addEventListener('fetch', event => {
+     case 'broadcast-accept-encoding':
+       broadcastAcceptEncoding(event);
+       return;
++    case 'record-media-range-request':
++      return recordMediaRangeRequest(event);
++    case 'use-media-range-request':
++      useMediaRangeRequest(event);
++      return;
+   }
+ });
+ 
+@@ -157,3 +162,57 @@ function broadcastAcceptEncoding(event) {
+   // Just send back any response, it isn't important for the test.
+   event.respondWith(new Response(''));
+ }
++
++let rangeResponse = {};
++
++async function recordMediaRangeRequest(event) {
++  /** @type Request */
++  const request = event.request;
++  const url = new URL(request.url);
++  const urlParams = new URLSearchParams(url.search);
++  const size = urlParams.get("size");
++  const id = urlParams.get('id');
++  const key = 'size' + size;
++
++  if (key in rangeResponse) {
++    // Don't re-fetch ranges we already have.
++    const clonedResponse = rangeResponse[key].clone();
++    event.respondWith(clonedResponse);
++  } else if (event.request.headers.get("range") === "bytes=0-") {
++    // Generate a bogus 206 response to trigger subsequent range requests
++    // of the desired size.
++    const length = urlParams.get("length") + 100;
++    const body = "A".repeat(Number(size));
++    event.respondWith(new Response(body, {status: 206, headers: {
++      "Content-Type": "audio/mp4",
++      "Content-Range": `bytes 0-1/${length}`
++    }}));
++  } else if (event.request.headers.get("range") === `bytes=${Number(size)}-`) {
++    // Pass through actual range requests which will attempt to fetch up to the
++    // length in the original response which is bigger than the actual resource
++    // to make sure 206 and 416 responses are treated the same.
++    rangeResponse[key] = await fetch(event.request);
++
++    // Let the client know we have the range response for the given ID
++    broadcast({id});
++  } else {
++    event.respondWith(Promise.reject(Error("Invalid Request")));
++  }
++}
++
++function useMediaRangeRequest(event) {
++  /** @type Request */
++  const request = event.request;
++  const url = new URL(request.url);
++  const urlParams = new URLSearchParams(url.search);
++  const size = urlParams.get("size");
++  const key = 'size' + size;
++
++  // Send a clone of the range response to preload.
++  if (key in rangeResponse) {
++    const clonedResponse = rangeResponse[key].clone();
++    event.respondWith(clonedResponse);
++  } else {
++    event.respondWith(Promise.reject(Error("Invalid Request")));
++  }
++}
+diff --git a/third_party/blink/web_tests/external/wpt/fetch/range/resources/utils.js b/third_party/blink/web_tests/external/wpt/fetch/range/resources/utils.js
+index 16ed737f63e8eee26a306c70acb0589e424db35d..ad2853b33dc7474293df1423dd8af459571736b9 100644
+--- a/third_party/blink/web_tests/external/wpt/fetch/range/resources/utils.js
++++ b/third_party/blink/web_tests/external/wpt/fetch/range/resources/utils.js
+@@ -8,6 +8,18 @@ function loadScript(url, { doc = document }={}) {
+   })
+ }
+ 
++function preloadImage(url, { doc = document }={}) {
++  return new Promise((resolve, reject) => {
++    const preload = doc.createElement('link');
++    preload.rel = 'preload';
++    preload.as = 'image';
++    preload.onload = () => resolve();
++    preload.onerror = () => resolve();
++    preload.href = url;
++    doc.body.appendChild(preload);
++  })
++}
++
+ /**
+  *
+  * @param {Document} document
+diff --git a/third_party/blink/web_tests/external/wpt/fetch/range/sw.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/fetch/range/sw.https.window-expected.txt
+index 134b0a7abd817599921d4fb430e8247a2cb40f82..a9577f01727678cd7a76bcc65e132fd6fcb230ac 100644
+--- a/third_party/blink/web_tests/external/wpt/fetch/range/sw.https.window-expected.txt
++++ b/third_party/blink/web_tests/external/wpt/fetch/range/sw.https.window-expected.txt
+@@ -4,6 +4,7 @@ PASS Defer range header passthrough tests to service worker
+ PASS Ranged response not allowed following no-cors ranged request
+ PASS Non-opaque ranged response executed
+ FAIL Accept-Encoding should not appear in a service worker assert_equals: Accept-Encoding should not be set for media expected (object) null but got (string) "identity;q=1, *;q=0"
++PASS Opaque range preload successes and failures should be indistinguishable
+ PASS Range headers correctly preserved
+ PASS Range headers correctly removed
+ PASS Headers correctly filtered
+diff --git a/third_party/blink/web_tests/external/wpt/fetch/range/sw.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/range/sw.https.window.js
+index 76f80e9416c615417ad2a9fbaa565641ff5b8a12..42e4ac6d75afdcbb2ad1e9d3e4069d9cbfd10dbd 100644
+--- a/third_party/blink/web_tests/external/wpt/fetch/range/sw.https.window.js
++++ b/third_party/blink/web_tests/external/wpt/fetch/range/sw.https.window.js
+@@ -149,3 +149,78 @@ promise_test(async t => {
+ 
+   assert_equals((await audioBroadcast).acceptEncoding, null, "Accept-Encoding should not be set for media");
+ }, `Accept-Encoding should not appear in a service worker`);
++
++promise_test(async t => {
++  const scope = BASE_SCOPE + Math.random();
++  await setupRegistration(t, scope);
++  const iframe = await with_iframe(scope);
++  const w = iframe.contentWindow;
++  const length = 100;
++  const count = 3;
++  const counts = {};
++
++  // test a single range request size
++  async function testSizedRange(size, partialResponseCode) {
++    const rangeId = Math.random() + '';
++    const rangeBroadcast = awaitMessage(w.navigator.serviceWorker, rangeId);
++
++    // Create a bogus audo element to trick the browser into sending
++    // cross-origin range requests that can be manipulated by the service worker.
++    const sound_url = new URL('partial-text.py', w.location);
++    sound_url.hostname = REMOTE_HOST;
++    sound_url.searchParams.set('action', 'record-media-range-request');
++    sound_url.searchParams.set('length', length);
++    sound_url.searchParams.set('size', size);
++    sound_url.searchParams.set('partial', partialResponseCode);
++    sound_url.searchParams.set('id', rangeId);
++    appendAudio(w.document, sound_url);
++
++    // wait for the range requests to happen
++    await rangeBroadcast;
++
++    // Create multiple preload requests and count the number of resource timing
++    // entries that get created to make sure 206 and 416 range responses are treated
++    // the same.
++    const url = new URL('partial-text.py', w.location);
++    url.searchParams.set('action', 'use-media-range-request');
++    url.searchParams.set('size', size);
++    counts['size' + size] = 0;
++    for (let i = 0; i < count; i++) {
++      await preloadImage(url, { doc: w.document });
++    }
++  }
++
++  // Test range requests from 1 smaller than the correct size to 1 larger than
++  // the correct size to exercise the various permutations using the default 206
++  // response code for successful range requests.
++  for (let size = length - 1; size <= length + 1; size++) {
++    await testSizedRange(size, '206');
++  }
++
++  // Test a successful range request using a 200 response.
++  await testSizedRange(length - 2, '200');
++
++  // Check the resource timing entries and count the reported number of fetches of each type
++  const resources = w.performance.getEntriesByType("resource");
++  for (const entry of resources) {
++    const url = new URL(entry.name);
++    if (url.searchParams.has('action') &&
++        url.searchParams.get('action') == 'use-media-range-request' &&
++        url.searchParams.has('size')) {
++      counts['size' + url.searchParams.get('size')]++;
++    }
++  }
++
++  // Make sure there are a non-zero number of preload requests and they are all the same
++  let counts_valid = true;
++  const first = 'size' + (length - 2);
++  for (let size = length - 2; size <= length + 1; size++) {
++    let key = 'size' + size;
++    if (!(key in counts) || counts[key] <= 0 || counts[key] != counts[first]) {
++      counts_valid = false;
++      break;
++    }
++  }
++
++  assert_true(counts_valid, `Opaque range request preloads were different for error and success`);
++}, `Opaque range preload successes and failures should be indistinguishable`);