Commit e3daff23 authored by Nicolas Pena's avatar Nicolas Pena Committed by Commit Bot

[ElementTiming] Use TimingAllowOrigin check and remove bubbling

This CL changes the security check for ElementTiming from same-origin to
TAO, which requires plumbing the OriginalTimingAllowOrigin attribute for
the case of 304 responses. It also removes bubbling to be consistent with
ResourceTiming. Some tests are fixed to properly use cross-origins within
external/wpt, and a cross-origin resource with TAO wildcard test is
added. More TAO tests will be added in a followup.

Bug: 928425, 879270
Change-Id: I4df40818823e3db9851fbc9586a0bda9c1adcfec
Reviewed-on: https://chromium-review.googlesource.com/c/1453074
Commit-Queue: Nicolás Peña Moreno <npm@chromium.org>
Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Reviewed-by: default avatarYoav Weiss <yoavweiss@chromium.org>
Cr-Commit-Position: refs/heads/master@{#630019}
parent 0342c90d
...@@ -61,10 +61,12 @@ void ImageElementTiming::NotifyImagePainted(const HTMLImageElement* element, ...@@ -61,10 +61,12 @@ void ImageElementTiming::NotifyImagePainted(const HTMLImageElement* element,
if (!layout_image->CachedImage()) if (!layout_image->CachedImage())
return; return;
const KURL& url = layout_image->CachedImage()->Url();
DCHECK(GetSupplementable()->document() == &layout_image->GetDocument()); DCHECK(GetSupplementable()->document() == &layout_image->GetDocument());
if (!SecurityOrigin::AreSameSchemeHostPort(layout_image->GetDocument().Url(), DCHECK(layout_image->GetDocument().GetSecurityOrigin());
url)) if (!Performance::PassesTimingAllowCheck(
layout_image->CachedImage()->GetResponse(),
*layout_image->GetDocument().GetSecurityOrigin(), AtomicString(),
&layout_image->GetDocument()))
return; return;
// Compute the viewport rect. // Compute the viewport rect.
...@@ -122,30 +124,15 @@ void ImageElementTiming::NotifyImagePainted(const HTMLImageElement* element, ...@@ -122,30 +124,15 @@ void ImageElementTiming::NotifyImagePainted(const HTMLImageElement* element,
void ImageElementTiming::ReportImagePaintSwapTime(WebLayerTreeView::SwapResult, void ImageElementTiming::ReportImagePaintSwapTime(WebLayerTreeView::SwapResult,
base::TimeTicks timestamp) { base::TimeTicks timestamp) {
Document* document = GetSupplementable()->document(); WindowPerformance* performance =
DCHECK(document); DOMWindowPerformance::performance(*GetSupplementable());
const SecurityOrigin* current_origin = document->GetSecurityOrigin(); if (performance && (performance->HasObserverFor(PerformanceEntry::kElement) ||
// It suffices to check the current origin against the parent origin since all performance->ShouldBufferEntries())) {
// origins stored in |element_timings_| have been checked against the current for (const auto& element_timing : element_timings_) {
// origin. performance->AddElementTiming(element_timing.name, element_timing.rect,
while (document && timestamp);
current_origin->IsSameSchemeHostPort(document->GetSecurityOrigin())) {
DCHECK(document->domWindow());
WindowPerformance* performance =
DOMWindowPerformance::performance(*document->domWindow());
if (performance &&
(performance->HasObserverFor(PerformanceEntry::kElement) ||
performance->ShouldBufferEntries())) {
for (const auto& element_timing : element_timings_) {
performance->AddElementTiming(element_timing.name, element_timing.rect,
timestamp);
}
} }
// Provide the entry to the parent documents for as long as the origin check
// still holds.
document = document->ParentDocument();
} }
element_timings_.clear(); element_timings_.clear();
} }
......
...@@ -250,6 +250,12 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData { ...@@ -250,6 +250,12 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
bool HasObserverFor(PerformanceEntry::EntryType) const; bool HasObserverFor(PerformanceEntry::EntryType) const;
// TODO(npm): is the AtomicString parameter here actually needed?
static bool PassesTimingAllowCheck(const ResourceResponse&,
const SecurityOrigin&,
const AtomicString&,
ExecutionContext*);
static bool AllowsTimingRedirect(const Vector<ResourceResponse>&, static bool AllowsTimingRedirect(const Vector<ResourceResponse>&,
const ResourceResponse&, const ResourceResponse&,
const SecurityOrigin&, const SecurityOrigin&,
...@@ -260,11 +266,6 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData { ...@@ -260,11 +266,6 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
void Trace(blink::Visitor*) override; void Trace(blink::Visitor*) override;
private: private:
static bool PassesTimingAllowCheck(const ResourceResponse&,
const SecurityOrigin&,
const AtomicString&,
ExecutionContext*);
void AddPaintTiming(PerformancePaintTiming::PaintType, TimeTicks start_time); void AddPaintTiming(PerformancePaintTiming::PaintType, TimeTicks start_time);
PerformanceMeasure* MeasureInternal( PerformanceMeasure* MeasureInternal(
......
...@@ -16,17 +16,20 @@ ...@@ -16,17 +16,20 @@
// We add the image during onload to be sure that the observer is registered // We add the image during onload to be sure that the observer is registered
// in time for it to observe the element timing. // in time for it to observe the element timing.
// TODO(npm): change observer to use buffered flag. // TODO(npm): change observer to use buffered flag.
window.onload = () => { window.onload = t.step_func(() => {
// Add a cross origin image resource. // Add a cross origin image resource.
const img = document.createElement('img'); const img = document.createElement('img');
img.src = img.src = 'http://{{domains[www]}}:{{ports[http][1]}}'
'http://localhost:8000/resources/square100.png'; + '/element-timing/resources/square100.png';
img.setAttribute('elementtiming', 'my_image');
img.onload = t.step_func(() => {
t.step_timeout( () => {
// After some wait, assume observer did not receive the entry, so the test passes.
t.done();
}, 100);
});
document.body.appendChild(img); document.body.appendChild(img);
}; });
t.step_timeout( () => {
// After some wait, assume observer did not receive the entry, so the test passes.
t.done();
}, 100);
}, 'Cross-origin image element is NOT observable.'); }, 'Cross-origin image element is NOT observable.');
</script> </script>
......
...@@ -16,17 +16,19 @@ ...@@ -16,17 +16,19 @@
// We add the iframe during onload to be sure that the observer is registered // We add the iframe during onload to be sure that the observer is registered
// in time for it to observe the element timing. // in time for it to observe the element timing.
// TODO(npm): change observer to use buffered flag. // TODO(npm): change observer to use buffered flag.
window.onload = () => { window.onload = t.step_func(() => {
// Add a cross origin iframe with an image. // Add a cross origin iframe with an image.
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
iframe.src = iframe.src = 'http://{{domains[www]}}:{{ports[http][1]}}'
'http://localhost:8000/performance-timing/element-resources/iframe-with-square.html'; + '/element-timing/resources/iframe-with-square.html';
document.body.appendChild(iframe); document.body.appendChild(iframe);
}; iframe.onload = t.step_func(() => {
t.step_timeout( () => { t.step_timeout( () => {
// After some wait, assume observer did not receive the entry, so the test passes. // After some wait, assume observer did not receive the entry, so the test passes.
t.done(); t.done();
}, 300); }, 100);
});
});
}, 'Element from cross origin iframe is NOT observable.'); }, 'Element from cross origin iframe is NOT observable.');
</script> </script>
......
<!DOCTYPE HTML>
<meta charset=utf-8>
<title>Element Timing: observe elements from same-origin iframes</title>
<body>
<style>
body {
margin: 0;
}
</style>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/element-timing-helpers.js"></script>
<script>
async_test((t) => {
let beforeRender;
const observer = new PerformanceObserver(
t.step_func_done((entryList) => {
assert_equals(entryList.getEntries().length, 1);
const entry = entryList.getEntries()[0];
checkElement(entry, 'my_image', beforeRender);
// Assume viewport has size at least 20, so the element is fully visible.
checkRect(entry, [0, 20, 0, 20]);
})
);
observer.observe({entryTypes: ['element']});
// We add the image during onload to be sure that the observer is registered
// in time for it to observe the element timing.
// TODO(npm): change observer to use buffered flag.
window.onload = t.step_func(() => {
const img = document.createElement('img');
img.src = 'http://{{domains[www]}}:{{ports[http][1]}}/'
+ 'element-timing/resources/TAOImage.py?tao=wildcard';
img.setAttribute('elementtiming', 'my_image');
img.onload = t.step_func(() => {
// After a short delay, assume that the entry was not dispatched.
t.step_timeout(() => {
assert_unreached("Should have received an entry!");
t.done();
}, 100);
});
document.body.appendChild(img);
beforeRender = performance.now();
});
}, 'Cross-origin element with wildcard TAO is observed.');
</script>
</body>
...@@ -11,15 +11,10 @@ body { ...@@ -11,15 +11,10 @@ body {
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>
<script src="resources/element-timing-helpers.js"></script> <script src="resources/element-timing-helpers.js"></script>
<script> <script>
let beforeRender;
async_test((t) => { async_test((t) => {
const observer = new PerformanceObserver( const observer = new PerformanceObserver(
t.step_func_done((entryList) => { t.step_func_done((entryList) => {
assert_equals(entryList.getEntries().length, 1); assert_unreached("Should not have received an entry!");
const entry = entryList.getEntries()[0];
checkElement(entry, 'my_image', beforeRender);
// Assume viewport has size at least 100, so the element is fully visible.
checkRect(entry, [0, 100, 0, 100]);
}) })
); );
observer.observe({entryTypes: ['element']}); observer.observe({entryTypes: ['element']});
...@@ -30,10 +25,16 @@ body { ...@@ -30,10 +25,16 @@ body {
// Add iframe with an image of width and height equal to 100. // Add iframe with an image of width and height equal to 100.
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
iframe.src = 'resources/iframe-with-square.html'; iframe.src = 'resources/iframe-with-square.html';
iframe.onload = () => {
// After a short delay, assume that the entry was not dispatched to the
// parent frame.
t.step_timeout(() => {
t.done();
}, 100);
}
document.body.appendChild(iframe); document.body.appendChild(iframe);
beforeRender = performance.now();
}; };
}, 'Element from same-origin iframe is observable.'); }, 'Element in child iframe is not observed, even if same-origin.');
</script> </script>
</body> </body>
import os
def main(request, response):
origin = request.GET.first('origin', '');
if origin:
response.headers.set('Access-Control-Allow-Origin', origin)
tao = request.GET.first('tao')
if tao == 'wildcard':
# wildcard, pass
response.headers.set('Timing-Allow-Origin', '*')
elif tao == 'null':
# null, fail
response.headers.set('Timing-Allow-Origin', 'null')
elif tao == 'origin':
# case-sensitive match for origin, pass
response.headers.set('Timing-Allow-Origin', origin)
elif tao == 'space':
# space separated list of origin and wildcard, fail
response.headers.set('Timing-Allow-Origin', (origin + ' *'))
elif tao == 'multi':
# more than one TAO values, separated by comma, pass
response.headers.set('Timing-Allow-Origin', origin)
response.headers.append('Timing-Allow-Origin', '*')
elif tao == 'multi_wildcard':
# multiple wildcards, separated by comma, pass
response.headers.set('Timing-Allow-Origin', '*')
response.headers.append('Timing-Allow-Origin', '*')
elif tao == 'match_origin':
# contains a match of origin, separated by comma, pass
response.headers.set('Timing-Allow-Origin', origin)
response.headers.append('Timing-Allow-Origin', "fake")
elif tao == 'match_wildcard':
# contains a wildcard, separated by comma, pass
response.headers.set('Timing-Allow-Origin', "fake")
response.headers.append('Timing-Allow-Origin', '*')
elif tao == 'uppercase':
# non-case-sensitive match for origin, fail
response.headers.set('Timing-Allow-Origin', origin.upper())
else:
pass
response.headers.set("Cache-Control", "no-cache, must-revalidate");
image_path = os.path.join(os.path.dirname(__file__), "square20.png");
response.content = open(image_path, mode='rb').read();
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment