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,
if (!layout_image->CachedImage())
return;
const KURL& url = layout_image->CachedImage()->Url();
DCHECK(GetSupplementable()->document() == &layout_image->GetDocument());
if (!SecurityOrigin::AreSameSchemeHostPort(layout_image->GetDocument().Url(),
url))
DCHECK(layout_image->GetDocument().GetSecurityOrigin());
if (!Performance::PassesTimingAllowCheck(
layout_image->CachedImage()->GetResponse(),
*layout_image->GetDocument().GetSecurityOrigin(), AtomicString(),
&layout_image->GetDocument()))
return;
// Compute the viewport rect.
......@@ -122,30 +124,15 @@ void ImageElementTiming::NotifyImagePainted(const HTMLImageElement* element,
void ImageElementTiming::ReportImagePaintSwapTime(WebLayerTreeView::SwapResult,
base::TimeTicks timestamp) {
Document* document = GetSupplementable()->document();
DCHECK(document);
const SecurityOrigin* current_origin = document->GetSecurityOrigin();
// It suffices to check the current origin against the parent origin since all
// origins stored in |element_timings_| have been checked against the current
// origin.
while (document &&
current_origin->IsSameSchemeHostPort(document->GetSecurityOrigin())) {
DCHECK(document->domWindow());
WindowPerformance* performance =
DOMWindowPerformance::performance(*document->domWindow());
if (performance &&
(performance->HasObserverFor(PerformanceEntry::kElement) ||
DOMWindowPerformance::performance(*GetSupplementable());
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();
}
......
......@@ -250,6 +250,12 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
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>&,
const ResourceResponse&,
const SecurityOrigin&,
......@@ -260,11 +266,6 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
void Trace(blink::Visitor*) override;
private:
static bool PassesTimingAllowCheck(const ResourceResponse&,
const SecurityOrigin&,
const AtomicString&,
ExecutionContext*);
void AddPaintTiming(PerformancePaintTiming::PaintType, TimeTicks start_time);
PerformanceMeasure* MeasureInternal(
......
......@@ -16,17 +16,20 @@
// 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 = () => {
window.onload = t.step_func(() => {
// Add a cross origin image resource.
const img = document.createElement('img');
img.src =
'http://localhost:8000/resources/square100.png';
document.body.appendChild(img);
};
img.src = 'http://{{domains[www]}}:{{ports[http][1]}}'
+ '/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);
});
}, 'Cross-origin image element is NOT observable.');
</script>
......
......@@ -16,17 +16,19 @@
// We add the iframe 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 = () => {
window.onload = t.step_func(() => {
// Add a cross origin iframe with an image.
const iframe = document.createElement('iframe');
iframe.src =
'http://localhost:8000/performance-timing/element-resources/iframe-with-square.html';
iframe.src = 'http://{{domains[www]}}:{{ports[http][1]}}'
+ '/element-timing/resources/iframe-with-square.html';
document.body.appendChild(iframe);
};
iframe.onload = t.step_func(() => {
t.step_timeout( () => {
// After some wait, assume observer did not receive the entry, so the test passes.
t.done();
}, 300);
}, 100);
});
});
}, 'Element from cross origin iframe is NOT observable.');
</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 {
<script src="/resources/testharnessreport.js"></script>
<script src="resources/element-timing-helpers.js"></script>
<script>
let beforeRender;
async_test((t) => {
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 100, so the element is fully visible.
checkRect(entry, [0, 100, 0, 100]);
assert_unreached("Should not have received an entry!");
})
);
observer.observe({entryTypes: ['element']});
......@@ -30,10 +25,16 @@ body {
// Add iframe with an image of width and height equal to 100.
const iframe = document.createElement('iframe');
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);
beforeRender = performance.now();
};
}, 'Element from same-origin iframe is observable.');
}, 'Element in child iframe is not observed, even if same-origin.');
</script>
</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