Commit 7cfae578 authored by Nicolas Pena's avatar Nicolas Pena Committed by Commit Bot

Prepare to move EventTiming tests to WPT

This CL does most of the changes needed to move EventTiming tests to WPT. The CL
attempts to make the tests less flaky by ensuring the event handlers themselves
take a long time to execute.

Bug: 908187, 907948, 907949
Change-Id: Iced25d635cb05c9903a209d43988ba4e71681195
Reviewed-on: https://chromium-review.googlesource.com/c/1351600Reviewed-by: default avatarTimothy Dresser <tdresser@chromium.org>
Commit-Queue: Nicolás Peña Moreno <npm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#611099}
parent 6c181a47
...@@ -3,47 +3,47 @@ ...@@ -3,47 +3,47 @@
<meta charset=utf-8 /> <meta charset=utf-8 />
<title>Event Timing: buffer long-latency events before onload</title> <title>Event Timing: buffer long-latency events before onload</title>
<button id='button' onclick='clickDelay()'>Generate a 'click' event</button> <button id='button' onclick='clickDelay()'>Generate a 'click' event</button>
<script src=../../resources/testharness.js></script> <script src=/resources/testharness.js></script>
<script src=../../resources/testharnessreport.js></script> <script src=/resources/testharnessreport.js></script>
<script src=./resources/event-timing-support.js></script> <script src=resources/event-timing-support.js></script>
<img src=./resources/slow-image.php> <img src=resources/slow-image.php>
<script> <script>
let clickTimeMin; let clickTimeMin;
let processingStartMin; let processingStartMin;
let onloadStart;
let firstClickStart = 0; let firstClickStart = 0;
let firstClickEnd = 0; let firstClickEnd = 0;
function clickDelay() { function clickDelay() {
const onclickStart = performance.now(); const onclickStart = performance.now();
if (firstClickStart == 0) if (firstClickStart === 0)
firstClickStart = onclickStart; firstClickStart = onclickStart;
while(performance.now() < onclickStart + 10) {} while(performance.now() < onclickStart + 60) {}
if (firstClickEnd == 0) if (firstClickEnd === 0)
firstClickEnd = performance.now(); firstClickEnd = performance.now();
} }
function validateEntries() { function validateEntries() {
const entries = performance.getEntriesByName('click', 'event'); const entries = performance.getEntriesByName('click', 'event');
const onloadTime = performance.timing.loadEventStart - performance.timeOrigin;
const entriesBeforeOnload = entries.filter( const entriesBeforeOnload = entries.filter(
e => e.startTime < onloadTime); e => e.startTime < onloadStart);
assert_equals(entriesBeforeOnload.length, 1, assert_equals(entriesBeforeOnload.length, 1,
"Long latency events before onload should be buffered."); "Long latency events before onload should be buffered.");
const entry = entriesBeforeOnload[0]; const entry = entriesBeforeOnload[0];
verifyClickEvent(entry, true); verifyClickEvent(entry, true);
assert_greater_than(entry.processingStart, processingStartMin, assert_greater_than_equal(entry.startTime, clickTimeMin,
"The entry should be processed later than processingStartMin.");
assert_greater_than(entry.startTime, clickTimeMin,
"The entry's start time should be later than clickTimeMin."); "The entry's start time should be later than clickTimeMin.");
assert_greater_than_equal(firstClickStart, entry.processingStart, assert_greater_than_equal(entry.processingStart, processingStartMin,
"The processingStart must be before the onclick starts.") "The entry should be processed later than processingStartMin.");
assert_less_than_equal(entry.processingStart, firstClickStart,
"The processingStart must be before firstClickStart.")
assert_greater_than_equal(entry.processingEnd, firstClickEnd, assert_greater_than_equal(entry.processingEnd, firstClickEnd,
"The processingEnd must be after onclick finishes."); "The processingEnd must be after firstClickEnd.");
const entriesAfterOnload = entries.filter( const entriesAfterOnload = entries.filter(
e => e.startTime > onloadTime); e => e.startTime >= onloadStart);
assert_equals(entriesAfterOnload.length, 0, assert_equals(entriesAfterOnload.length, 0,
"Events after onload shouldn't be buffered."); "Events after onload shouldn't be buffered.");
} }
...@@ -62,8 +62,11 @@ ...@@ -62,8 +62,11 @@
async_test(function(t) { async_test(function(t) {
clickTimeMin = performance.now(); clickTimeMin = performance.now();
clickAndBlockMain('button'); clickAndBlockMain('button');
// Event handlers will be dispatched asynchronously, so this will be called
// before processing begins.
processingStartMin = performance.now(); processingStartMin = performance.now();
on_event(window, 'load', e => { on_event(window, 'load', e => {
onloadStart = performance.now();
clickAndBlockMain('button').then(wait).then( clickAndBlockMain('button').then(wait).then(
t.step_func_done(validateEntries)); t.step_func_done(validateEntries));
}); });
......
...@@ -7,22 +7,19 @@ ...@@ -7,22 +7,19 @@
</head> </head>
<body> <body>
<button id='button' onclick='1'>Generate a 'click' event</button> <button id='button' onclick='1'>Generate a 'click' event</button>
</body> <script src=/resources/testharness.js></script>
<div> <script src=/resources/testharnessreport.js></script>
<iframe src=./resources/event-timing-crossiframe-childframe.html></iframe> <script src=resources/event-timing-support.js></script>
</div>
<script src=../../../resources/testharness.js></script>
<script src=../../../resources/testharnessreport.js></script>
<script src=./resources/event-timing-support.js></script>
<img src=./resources/slow-image.php> <img src=./resources/slow-image.php>
<iframe src=resources/event-timing-crossiframe-childframe.html></iframe>
<script> <script>
let clickTimeMin; let clickTimeMin;
let processingStartMin; let processingStartMin;
let onloadStart;
function validateEntries() { function validateEntries() {
const entries = performance.getEntriesByName('click', 'event'); const entries = performance.getEntriesByName('click', 'event');
const onloadTime = performance.timing.loadEventStart - performance.timeOrigin;
assert_equals(entries.length, 1, assert_equals(entries.length, 1,
"Observer of main frames should only capture main-frame event-timing entries." "Observer of main frames should only capture main-frame event-timing entries."
...@@ -32,11 +29,11 @@ ...@@ -32,11 +29,11 @@
assert_greater_than(entry.processingStart, processingStartMin, assert_greater_than(entry.processingStart, processingStartMin,
"The entry's processing start should be later than processingStartMin."); "The entry's processing start should be later than processingStartMin.");
assert_greater_than(onloadTime, entry.processingStart, assert_greater_than(onloadStart, entry.processingStart,
"onload should occur later than the entry's procesisng start."); "onload should occur later than the entry's procesisng start.");
assert_greater_than(entry.startTime, clickTimeMin, assert_greater_than(entry.startTime, clickTimeMin,
"The entry's start time should be later than clickTimeMin."); "The entry's start time should be later than clickTimeMin.");
assert_greater_than(onloadTime, entry.startTime, assert_greater_than(onloadStart, entry.startTime,
"onload should occur later than the entry's start time."); "onload should occur later than the entry's start time.");
} }
...@@ -49,13 +46,13 @@ ...@@ -49,13 +46,13 @@
assert_greater_than(entry.processingStart, childFrameData.processingStartMin, assert_greater_than(entry.processingStart, childFrameData.processingStartMin,
"The entry's processing start should be later than the child frame's processingStartMin."); "The entry's processing start should be later than the child frame's processingStartMin.");
assert_greater_than(childFrameData.onloadTime, entry.processingStart, assert_greater_than(childFrameData.onloadStart, entry.processingStart,
"Child frame's onload should occur later than the entry's processing \ "Child frame's onload should occur later than the entry's processing \
start."); start.");
assert_greater_than(entry.startTime, childFrameData.clickTimeMin, assert_greater_than(entry.startTime, childFrameData.clickTimeMin,
"The entry's start time should be later than the child frame's \ "The entry's start time should be later than the child frame's \
clickTimeMin."); clickTimeMin.");
assert_greater_than(childFrameData.onloadTime, entry.startTime, assert_greater_than(childFrameData.onloadStart, entry.startTime,
"Child frame's onload should be later than the entry's start time."); "Child frame's onload should be later than the entry's start time.");
assert_array_equals(childFrameData.observedEntries, assert_array_equals(childFrameData.observedEntries,
...@@ -73,6 +70,7 @@ ...@@ -73,6 +70,7 @@
}, false); }, false);
}); });
on_event(window, 'load', e => { on_event(window, 'load', e => {
onloadStart = performance.now();
childFrameEntriesPromise.then((entries) => { childFrameEntriesPromise.then((entries) => {
t.step(() => { t.step(() => {
validateChildFrameEntries(entries); validateChildFrameEntries(entries);
...@@ -84,5 +82,5 @@ ...@@ -84,5 +82,5 @@
}, "Event Timing: entries should only be observable by its own frame."); }, "Event Timing: entries should only be observable by its own frame.");
</script> </script>
</body>
</html> </html>
...@@ -4,25 +4,28 @@ ...@@ -4,25 +4,28 @@
<title>Event Timing: Performance observers can observe long-latency events <title>Event Timing: Performance observers can observe long-latency events
</title> </title>
<button id='button' onclick='1'>Generate a 'click' event</button> <button id='button' onclick='1'>Generate a 'click' event</button>
<script src=../../../resources/testharness.js></script> <script src=/resources/testharness.js></script>
<script src=../../../resources/testharnessreport.js></script> <script src=/resources/testharnessreport.js></script>
<script src=./resources/event-timing-support.js></script> <script src=resources/event-timing-support.js></script>
<img src=./resources/slow-image.php> <img src=resources/slow-image.php>
<script> <script>
let timeBeforeFirstClick;
let timeAfterFirstClick; let timeAfterFirstClick;
let timeAfterSecondClick;
let onloadStart;
let observedEntries = []; let observedEntries = [];
function verifyBuffer(bufferedEntries) { function verifyBuffer(bufferedEntries) {
const loadStart =
performance.timing.loadEventStart - performance.timeOrigin;
assert_equals(bufferedEntries.length, 1, assert_equals(bufferedEntries.length, 1,
"Only events before onload should be buffered."); "Only events before onload should be buffered.");
const entry = bufferedEntries[0]; const entry = bufferedEntries[0];
assert_greater_than(loadStart, entry.startTime, assert_greater_than(onloadStart, entry.startTime,
"onload should be later than entry's start time."); "Onload should be later than entry's start time.");
assert_greater_than(entry.processingStart, timeAfterFirstClick, assert_greater_than(entry.processingStart, timeBeforeFirstClick,
"The entry's processing start should be later than timeAfterFirstClick."); "The entry's processing start should be after timeBeforeFirstClick");
assert_less_than(entry.processingStart, timeAfterFirstClick,
"The entry's processing start should be before timeAfterFirstClick.");
verifyClickEvent(entry, true); verifyClickEvent(entry, true);
} }
...@@ -34,9 +37,11 @@ ...@@ -34,9 +37,11 @@
const entry1 = entriesAfterFirstClick[0]; const entry1 = entriesAfterFirstClick[0];
verifyClickEvent(entry1); verifyClickEvent(entry1);
assert_greater_than(entry1.processingStart, timeAfterFirstClick, assert_greater_than(entry1.processingStart, timeAfterFirstClick,
"entry1's processing start should be later than timeAfterFirstClick."); "entry1's processing start should be after timeAfterFirstClick");
assert_greater_than(entry1.startTime, timeAfterFirstClick, assert_less_than(entry1.processingStart, timeAfterSecondClick,
"entry1's start time should be later than timeAfterFirstClick."); "entry1's processing start should be before timeAfterSecondClick.");
assert_greater_than(entry1.startTime, onloadStart,
"entry1's start time should be later than onloadStart.");
const entriesBeforeFirstClick = const entriesBeforeFirstClick =
observedEntries.filter(e => e.startTime < timeAfterFirstClick); observedEntries.filter(e => e.startTime < timeAfterFirstClick);
...@@ -45,8 +50,10 @@ ...@@ -45,8 +50,10 @@
); );
const entry2 = entriesBeforeFirstClick[0]; const entry2 = entriesBeforeFirstClick[0];
verifyClickEvent(entry2); verifyClickEvent(entry2);
assert_greater_than(entry2.processingStart, timeAfterFirstClick, assert_greater_than(entry2.processingStart, timeBeforeFirstClick,
"entry2's processing start should be later than timeAfterFirstClick."); "entry2's processing start should be after timeBeforeFirstClick");
assert_less_than(entry2.processingStart, timeAfterFirstClick,
"entry2's processing start should be berfore timeAfterFirstClick.");
assert_greater_than(timeAfterFirstClick, entry2.startTime, assert_greater_than(timeAfterFirstClick, entry2.startTime,
"timeAfterFirstClick should be later than entry2's start time."); "timeAfterFirstClick should be later than entry2's start time.");
} }
...@@ -72,12 +79,16 @@ ...@@ -72,12 +79,16 @@
resolve(observedEntries); resolve(observedEntries);
}).observe({ entryTypes: ['event'] }); }).observe({ entryTypes: ['event'] });
}); });
clickAndBlockMain('button'); timeBeforeFirstClick = performance.now();
timeAfterFirstClick = performance.now(); clickAndBlockMain('button').then( () => {
timeAfterFirstClick = performance.now();
});
on_event(window, 'load', function(e) { on_event(window, 'load', function(e) {
onloadStart = performance.now();
// After onload start and before registering observer. // After onload start and before registering observer.
const bufferPromise = clickAndBlockMain('button').then(wait); const bufferPromise = clickAndBlockMain('button').then(wait);
Promise.all([observerPromise, bufferPromise]).then((results) => { Promise.all([observerPromise, bufferPromise]).then((results) => {
timeAfterSecondClick = performance.now();
t.step(verifyObserverEntries.bind(null, results[0])); t.step(verifyObserverEntries.bind(null, results[0]));
t.step(verifyBuffer.bind(null, performance.getEntriesByName('click', 'event'))); t.step(verifyBuffer.bind(null, performance.getEntriesByName('click', 'event')));
t.done(); t.done();
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
<meta charset=utf-8 /> <meta charset=utf-8 />
<title>Event Timing: buffer long-latency events before onload</title> <title>Event Timing: buffer long-latency events before onload</title>
<button id='button' onclick='1'>Generate a 'click' event</button> <button id='button' onclick='1'>Generate a 'click' event</button>
<script src=../../resources/testharness.js></script> <script src=/resources/testharness.js></script>
<script src=../../resources/testharnessreport.js></script> <script src=/resources/testharnessreport.js></script>
<script src=./resources/event-timing-support.js></script> <script src=resources/event-timing-support.js></script>
<script> <script>
/* Timeline: /* Timeline:
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
registration are lost registration are lost
</title> </title>
<button id='button' onclick='1'>Generate a 'click' event</button> <button id='button' onclick='1'>Generate a 'click' event</button>
<script src=../../../resources/testharness.js></script> <script src=/resources/testharness.js></script>
<script src=../../../resources/testharnessreport.js></script> <script src=/resources/testharnessreport.js></script>
<script src=./resources/event-timing-support.js></script> <script src=resources/event-timing-support.js></script>
<script> <script>
let callbackTime; let callbackTime;
let observerStart; let observerStart;
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
<meta charset=utf-8 /> <meta charset=utf-8 />
<title>Event Timing: only observe the first input</title> <title>Event Timing: only observe the first input</title>
<button id='button' onclick='1'>Generate a 'click' event</button> <button id='button' onclick='1'>Generate a 'click' event</button>
<script src=../../resources/testharness.js></script> <script src=/resources/testharness.js></script>
<script src=../../resources/testharnessreport.js></script> <script src=/resources/testharnessreport.js></script>
<script src=./resources/event-timing-support.js></script> <script src=resources/event-timing-support.js></script>
<script> <script>
/* Test: /* Test:
...@@ -27,19 +27,20 @@ ...@@ -27,19 +27,20 @@
assert_equals(observedEntries[0].name, 'click'); assert_equals(observedEntries[0].name, 'click');
})).observe({ entryTypes: ['firstInput'] }); })).observe({ entryTypes: ['firstInput'] });
on_event(window, 'load', () => { on_event(window, 'load', () => {
clickAndBlockMain('button').then(wait).then(() => { clickAndBlockMain('button').then(() => {
clickAndBlockMain('button').then(wait); clickAndBlockMain('button').then(wait).then( () => {
// After some wait, the PerformanceObserver should have processed both clicks. // After some wait, the PerformanceObserver should have processed both clicks.
// One and only one firstInput entry should have been dispatched, so // One and only one firstInput entry should have been dispatched, so
// |hasObservedFirstInput| should be true. // |hasObservedFirstInput| should be true.
t.step_timeout( () => { t.step_timeout( () => {
assert_true(hasObservedFirstInput); assert_true(hasObservedFirstInput);
t.done(); t.done();
}, 100); }, 10);
});
}); });
}); });
}, },
"Event Timing: check firstInput for a PerformanceObserver observing only firstInput." "Event Timing: check firstInput for a PerformanceObserver observing only firstInput."
); );
</script> </script>
</html> </html>
\ No newline at end of file
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
<meta charset=utf-8> <meta charset=utf-8>
<title>Event Timing: make sure event-timing entries are retrievable by existing perf APIs.</title> <title>Event Timing: make sure event-timing entries are retrievable by existing perf APIs.</title>
<button id='button' onclick='1'>Generate a 'click' event</button> <button id='button' onclick='1'>Generate a 'click' event</button>
<script src=../../../resources/testharness.js></script> <script src=/resources/testharness.js></script>
<script src=../../../resources/testharnessreport.js></script> <script src=/resources/testharnessreport.js></script>
<script src=./resources/event-timing-support.js></script> <script src=resources/event-timing-support.js></script>
<img src=./resources/slow-image.php> <img src=resources/slow-image.php>
<script> <script>
function validateEntries() { function validateEntries() {
......
...@@ -3,63 +3,52 @@ ...@@ -3,63 +3,52 @@
<meta charset=utf-8 /> <meta charset=utf-8 />
<title>Event Timing only times certain types of trusted event. <title>Event Timing only times certain types of trusted event.
</title> </title>
<button id='button' onclick='mainThreadBusy(100)' <button id='button' onclick='mainThreadBusy(60)'
onfocus='mainThreadBusy(100)'>Generate a 'click' event</button> onfocus='mainThreadBusy(60)'>Generate a 'click' event</button>
<script src=../../../resources/testharness.js></script> <script src=/resources/testharness.js></script>
<script src=../../../resources/testharnessreport.js></script> <script src=/resources/testharnessreport.js></script>
<script src=./resources/event-timing-support.js></script> <script src=resources/event-timing-support.js></script>
<script> <script>
let trustedClickStart = 0; let trustedClickStart;
function trustedClickAndBlockMain(id) { function trustedClickAndBlockMain(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
trustedClickStart = performance.now(); trustedClickStart = performance.now();
clickOnElement(id); clickOnElement(id, resolve);
mainThreadBusy(100);
resolve();
}); });
} }
function untrustedClickAndBlockMain(id) { function untrustedClickAndBlockMain(id) {
return new Promise((resolve, reject) => { const target = document.getElementById(id);
const target = document.getElementById(id); // Block mainthread in the callback, as dispatchEvent() is a sync call.
target.dispatchEvent(new MouseEvent('click')); target.dispatchEvent(new MouseEvent('click'));
// Block mainthread in the callback, as dispatchEvent() is an sync call.
resolve();
});
} }
function trustedFocusAndBlockMain(id) { function trustedFocusAndBlockMain(id) {
return new Promise((resolve, reject) => { const target = document.getElementById(id);
const target = document.getElementById(id); trustedFocusStart = performance.now();
trustedFocusStart = performance.now(); // Block mainthread in the callback, as focus() is a sync call.
target.focus(); target.focus();
// Block mainthread in the callback, as focus() is an sync call.
resolve();
});
} }
async_test(function(t) { async_test(function(t) {
new PerformanceObserver(entryList => { new PerformanceObserver(t.step_func_done(entryList => {
const observerCallbackTime = performance.now(); const observerCallbackTime = performance.now();
const entries = entryList.getEntries(); const entries = entryList.getEntries();
t.step(()=>{ assert_equals(entries.length, 1,
assert_equals(entries.length, 1, "Should only observe one entry: " +
"Observe more than one entries: " + JSON.stringify(entries) + ".");
JSON.stringify(entries) + "."); assert_equals(entries[0].name, 'click',
assert_equals(entries[0].name, 'click', "The observed entry should be a click");
"The observed entry should be a click"); assert_less_than(entries[0].startTime, observerCallbackTime,
assert_greater_than(observerCallbackTime, entries[0].startTime, "The startTime should be before observerCallbackTime");
"assert(untrustedClickStart > entries[0].startTime) failed"); assert_greater_than(entries[0].startTime, trustedClickStart,
assert_greater_than(entries[0].startTime, trustedClickStart, "The startTime should be after trustedClickStart");
"assert(entries[0].startTime > trustedClickStart) failed"); })).observe({ entryTypes: ['event'] });
}); // Untrusted event of a type event timing cares about.
t.done(); untrustedClickAndBlockMain('button');
}).observe({ entryTypes: ['event'] }); // Trusted event of a type event timing doesn't cares about.
// untrusted event of a type event timing cares about trustedFocusAndBlockMain('button');
untrustedClickAndBlockMain('button').then(wait); // Trusted event of a type event timing cares about.
// trusted event of a type event timing doesn't cares about
trustedFocusAndBlockMain('button').then(wait);
// trusted event of a type event timing cares about
trustedClickAndBlockMain('button').then(wait); trustedClickAndBlockMain('button').then(wait);
}, "Event Timing only times certain types of trusted event."); }, "Event Timing only times certain types of trusted event.");
</script> </script>
......
...@@ -14,14 +14,14 @@ ...@@ -14,14 +14,14 @@
}); });
window.addEventListener('load', e => { window.addEventListener('load', e => {
observerPromise.then((observedEntries) => { observerPromise.then((observedEntries) => {
const onloadStart = performance.now();
const bufferedEntries = performance.getEntriesByType('event'); const bufferedEntries = performance.getEntriesByType('event');
const onloadTime = performance.timing.loadEventStart - performance.timeOrigin;
top.postMessage({ top.postMessage({
"bufferedEntries" : bufferedEntries, "bufferedEntries" : bufferedEntries,
"observedEntries": observedEntries, "observedEntries": observedEntries,
"clickTimeMin": clickTimeMin, "clickTimeMin": clickTimeMin,
"processingStartMin" : processingStartMin, "processingStartMin" : processingStartMin,
"onloadTime" : onloadTime, "onloadStart" : onloadStart,
}, '*'); }, '*');
}); });
}); });
......
// Clicks on the element with the given ID. It adds an event handler to the element
// which ensures that the events have a long duration and reported by EventTiming
// where appropriate.
function clickOnElement(id, callback) { function clickOnElement(id, callback) {
const element = document.getElementById(id); const element = document.getElementById(id);
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
...@@ -12,6 +15,7 @@ function clickOnElement(id, callback) { ...@@ -12,6 +15,7 @@ function clickOnElement(id, callback) {
] ]
}]; }];
var clickHandler = () => { var clickHandler = () => {
mainThreadBusy(60);
if (callback) if (callback)
callback(); callback();
element.removeEventListener("click", clickHandler); element.removeEventListener("click", clickHandler);
...@@ -29,7 +33,7 @@ function mainThreadBusy(duration) { ...@@ -29,7 +33,7 @@ function mainThreadBusy(duration) {
while (performance.now() < now + duration); while (performance.now() < now + duration);
} }
// This method should receive an entry of type 'event'. |is_false| is true only // This method should receive an entry of type 'event'. |is_first| is true only
// when the event also happens to correspond to the first event. In this case, // when the event also happens to correspond to the first event. In this case,
// the timings of the 'firstInput' entry should be equal to those of this entry. // the timings of the 'firstInput' entry should be equal to those of this entry.
function verifyClickEvent(entry, is_first=false) { function verifyClickEvent(entry, is_first=false) {
...@@ -69,6 +73,5 @@ function wait() { ...@@ -69,6 +73,5 @@ function wait() {
function clickAndBlockMain(id) { function clickAndBlockMain(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
clickOnElement(id, resolve); clickOnElement(id, resolve);
mainThreadBusy(300);
}); });
} }
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