Merge m-c to autoland

MozReview-Commit-ID: 4PrtfU4e6kL
This commit is contained in:
Phil Ringnalda 2017-05-21 13:17:22 -07:00
commit c62e9f1e9d
82 changed files with 2664 additions and 86 deletions

View File

@ -102,16 +102,17 @@ var SessionCookiesInternal = {
* cookies service and puts them into the store if they're session cookies.
*/
_ensureInitialized() {
if (!this._initialized) {
this._reloadCookies();
this._initialized = true;
Services.obs.addObserver(this, "cookie-changed");
// Listen for privacy level changes to reload cookies when needed.
Services.prefs.addObserver("browser.sessionstore.privacy_level", () => {
this._reloadCookies();
});
if (this._initialized) {
return;
}
this._reloadCookies();
this._initialized = true;
Services.obs.addObserver(this, "cookie-changed");
// Listen for privacy level changes to reload cookies when needed.
Services.prefs.addObserver("browser.sessionstore.privacy_level", () => {
this._reloadCookies();
});
},
/**
@ -172,7 +173,7 @@ var SessionCookiesInternal = {
return;
}
let iter = Services.cookies.enumerator;
let iter = Services.cookies.sessionEnumerator;
while (iter.hasMoreElements()) {
this._addCookie(iter.getNext());
}

View File

@ -569,6 +569,12 @@ JS_IsDeadWrapper(JSObject* obj)
return IsDeadProxyObject(obj);
}
JS_FRIEND_API(JSObject*)
JS_NewDeadWrapper(JSContext* cx, JSObject* origObj)
{
return NewDeadProxyObject(cx, origObj);
}
void
js::TraceWeakMaps(WeakMapTracer* trc)
{

View File

@ -96,6 +96,16 @@ JS_PCToLineNumber(JSScript* script, jsbytecode* pc, unsigned* columnp = nullptr)
extern JS_FRIEND_API(bool)
JS_IsDeadWrapper(JSObject* obj);
/**
* Creates a new dead wrapper object in the given scope. To be used when
* attempting to wrap objects from scopes which are already dead.
*
* If origObject is passed, it must be an proxy object, and will be
* used to determine the characteristics of the new dead wrapper.
*/
extern JS_FRIEND_API(JSObject*)
JS_NewDeadWrapper(JSContext* cx, JSObject* origObject = nullptr);
/*
* Used by the cycle collector to trace through a shape or object group and
* all cycle-participating data it reaches, using bounded stack space.

View File

@ -185,3 +185,37 @@ js::IsDeadProxyObject(JSObject* obj)
IsDerivedProxyObject(obj, DeadObjectProxy<DeadProxyIsCallableNotConstructor>::singleton()) ||
IsDerivedProxyObject(obj, DeadObjectProxy<DeadProxyNotCallableIsConstructor>::singleton());
}
const BaseProxyHandler*
js::SelectDeadProxyHandler(ProxyObject* obj)
{
// When nuking scripted proxies, isCallable and isConstructor values for
// the proxy needs to be preserved.
uint32_t callable = obj->handler()->isCallable(obj);
uint32_t constructor = obj->handler()->isConstructor(obj);
if (callable) {
if (constructor)
return DeadObjectProxy<DeadProxyIsCallableIsConstructor>::singleton();
return DeadObjectProxy<DeadProxyIsCallableNotConstructor>::singleton();
}
if (constructor)
return DeadObjectProxy<DeadProxyNotCallableIsConstructor>::singleton();
return DeadObjectProxy<DeadProxyNotCallableNotConstructor>::singleton();
}
JSObject*
js::NewDeadProxyObject(JSContext* cx, JSObject* origObj)
{
MOZ_ASSERT_IF(origObj, origObj->is<ProxyObject>());
const BaseProxyHandler* handler;
if (origObj && origObj->is<ProxyObject>())
handler = SelectDeadProxyHandler(&origObj->as<ProxyObject>());
else
handler = DeadObjectProxy<DeadProxyNotCallableNotConstructor>::singleton();
return NewProxyObject(cx, handler, NullHandleValue, nullptr, ProxyOptions());
}

View File

@ -11,6 +11,8 @@
namespace js {
class ProxyObject;
enum DeadProxyIsCallableIsConstructorOption
{
DeadProxyNotCallableNotConstructor,
@ -79,6 +81,12 @@ class DeadObjectProxy : public BaseProxyHandler
bool
IsDeadProxyObject(JSObject* obj);
const BaseProxyHandler*
SelectDeadProxyHandler(ProxyObject* obj);
JSObject*
NewDeadProxyObject(JSContext* cx, JSObject* origObj = nullptr);
} /* namespace js */
#endif /* proxy_DeadObjectProxy_h */

View File

@ -125,26 +125,15 @@ ProxyObject::setSameCompartmentPrivate(const Value& priv)
void
ProxyObject::nuke()
{
// When nuking scripted proxies, isCallable and isConstructor values for
// the proxy needs to be preserved. Do this before clearing the target.
uint32_t callable = handler()->isCallable(this);
uint32_t constructor = handler()->isConstructor(this);
// Select a dead proxy handler based on the properties of this wrapper.
// Do this before clearing the target.
const BaseProxyHandler* handler = SelectDeadProxyHandler(this);
// Clear the target reference.
setSameCompartmentPrivate(NullValue());
// Update the handler to make this a DeadObjectProxy.
if (callable) {
if (constructor)
setHandler(DeadObjectProxy<DeadProxyIsCallableIsConstructor>::singleton());
else
setHandler(DeadObjectProxy<DeadProxyIsCallableNotConstructor>::singleton());
} else {
if (constructor)
setHandler(DeadObjectProxy<DeadProxyNotCallableIsConstructor>::singleton());
else
setHandler(DeadObjectProxy<DeadProxyNotCallableNotConstructor>::singleton());
}
setHandler(handler);
// The proxy's reserved slots are not cleared and will continue to be
// traced. This avoids the possibility of triggering write barriers while

View File

@ -2841,10 +2841,11 @@ nsXPCComponents_Utils::IsDeadWrapper(HandleValue obj, bool* out)
if (obj.isPrimitive())
return NS_ERROR_INVALID_ARG;
// Make sure to unwrap first. Once a proxy is nuked, it ceases to be a
// wrapper, meaning that, if passed to another compartment, we'll generate
// a CCW for it. Make sure that IsDeadWrapper sees through the confusion.
*out = JS_IsDeadWrapper(js::CheckedUnwrap(&obj.toObject()));
// We should never have cross-compartment wrappers for dead wrappers.
MOZ_ASSERT_IF(js::IsCrossCompartmentWrapper(&obj.toObject()),
!JS_IsDeadWrapper(js::UncheckedUnwrap(&obj.toObject())));
*out = JS_IsDeadWrapper(&obj.toObject());
return NS_OK;
}

View File

@ -0,0 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1354733 */
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const global = this;
function run_test()
{
var sb = Cu.Sandbox(global);
let obj = new sb.Object();
Cu.nukeSandbox(sb);
ok(Cu.isDeadWrapper(obj), "object should be a dead wrapper");
// Create a new sandbox to wrap objects for.
var sb = Cu.Sandbox(global);
Cu.evalInSandbox(function echo(val) { return val; },
sb);
let echoed = sb.echo(obj);
ok(Cu.isDeadWrapper(echoed), "Rewrapped object should be a dead wrapper");
ok(echoed !== obj, "Rewrapped object should be a new dead wrapper");
ok(obj === obj, "Dead wrapper object should be equal to itself");
let liveObj = {};
ok(liveObj === sb.echo(liveObj), "Rewrapped live object should be equal to itself");
}

View File

@ -99,6 +99,7 @@ skip-if = toolkit == "android" # bug 1347431
[test_nuke_sandbox.js]
[test_nuke_sandbox_event_listeners.js]
[test_nuke_webextension_wrappers.js]
[test_rewrap_dead_wrapper.js]
[test_sandbox_metadata.js]
[test_exportFunction.js]
[test_promise.js]

View File

@ -181,6 +181,13 @@ WrapperFactory::PrepareForWrapping(JSContext* cx, HandleObject scope,
ExposeObjectToActiveJS(obj);
}
// If the object is a dead wrapper, return a new dead wrapper rather than
// trying to wrap it for a different compartment.
if (JS_IsDeadWrapper(obj)) {
retObj.set(JS_NewDeadWrapper(cx, obj));
return;
}
// If we've somehow gotten to this point after either the source or target
// compartment has been nuked, return a DeadObjectProxy to prevent further
// access.
@ -190,11 +197,7 @@ WrapperFactory::PrepareForWrapping(JSContext* cx, HandleObject scope,
CompartmentPrivate::Get(target)->wasNuked) {
NS_WARNING("Trying to create a wrapper into or out of a nuked compartment");
RootedObject ccw(cx, Wrapper::New(cx, obj, &CrossCompartmentWrapper::singleton));
NukeCrossCompartmentWrapper(cx, ccw);
retObj.set(ccw);
retObj.set(JS_NewDeadWrapper(cx));
return;
}

View File

@ -113,7 +113,7 @@ nsAbsoluteContainingBlock::RemoveFrame(nsIFrame* aDelegatingFrame,
void
nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
nsPresContext* aPresContext,
const ReflowInput& aReflowInput,
const ReflowInput& aReflowInput,
nsReflowStatus& aReflowStatus,
const nsRect& aContainingBlock,
AbsPosReflowFlags aFlags,
@ -130,11 +130,37 @@ nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
FrameDependsOnContainer(kidFrame,
!!(aFlags & AbsPosReflowFlags::eCBWidthChanged),
!!(aFlags & AbsPosReflowFlags::eCBHeightChanged));
nscoord availBSize = aReflowInput.AvailableBSize();
const nsRect& cb = isGrid ? nsGridContainerFrame::GridItemCB(kidFrame)
: aContainingBlock;
WritingMode containerWM = aReflowInput.GetWritingMode();
if (!kidNeedsReflow && availBSize != NS_UNCONSTRAINEDSIZE) {
// If we need to redo pagination on the kid, we need to reflow it.
// This can happen either if the available height shrunk and the
// kid (or its overflow that creates overflow containers) is now
// too large to fit in the available height, or if the available
// height has increased and the kid has a next-in-flow that we
// might need to pull from.
WritingMode kidWM = kidFrame->GetWritingMode();
if (containerWM.GetBlockDir() != kidWM.GetBlockDir()) {
// Not sure what the right test would be here.
kidNeedsReflow = true;
} else {
nscoord kidBEnd = kidFrame->GetLogicalRect(cb.Size()).BEnd(kidWM);
nscoord kidOverflowBEnd =
LogicalRect(containerWM,
kidFrame->GetScrollableOverflowRectRelativeToParent(),
aContainingBlock.Size()).BEnd(containerWM);
MOZ_ASSERT(kidOverflowBEnd >= kidBEnd);
if (kidOverflowBEnd > availBSize ||
(kidBEnd < availBSize && kidFrame->GetNextInFlow())) {
kidNeedsReflow = true;
}
}
}
if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
// Reflow the frame
nsReflowStatus kidStatus;
const nsRect& cb = isGrid ? nsGridContainerFrame::GridItemCB(kidFrame)
: aContainingBlock;
ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, cb,
aFlags, kidFrame, kidStatus, aOverflowAreas);
nsIFrame* nextFrame = kidFrame->GetNextInFlow();

View File

@ -0,0 +1,41 @@
<!DOCTYPE HTML>
<title>Test for dynamic re-pagination of absolutely positioned elements</title>
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
<style>
#multicol {
height: 100px;
width: 300px;
background: yellow;
position: relative;
}
#relpos {
position: absolute;
background: aqua;
height: 100px;
width: 90px;
top: 0;
left: 0;
}
#abspos {
position: absolute;
right: 0;
height: 80px;
width: 50px;
background: blue;
}
</style>
<div id="multicol">
<div id="relpos" style="left: 0">
<div id="abspos" style="top: 80px; height: 20px"></div>
</div>
<div id="relpos" style="left: 105px">
<div id="abspos" style="top: 0px; height: 60px"></div>
</div>
<div id="relpos" style="left: 210px; height: 50px">
</div>
</div>

View File

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<title>Test for dynamic re-pagination of absolutely positioned elements</title>
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
<style>
#multicol {
columns: 3;
column-fill: auto;
column-gap: 15px;
height: 150px;
width: 300px;
background: yellow;
}
#relpos {
position: relative;
background: aqua;
height: 250px;
}
#abspos {
position: absolute;
top: 80px;
right: 0;
height: 80px;
width: 50px;
background: blue;
}
</style>
<div id="multicol">
<div id="relpos">
<div id="abspos"></div>
</div>
</div>
<script>
var mc = document.getElementById("multicol");
mc.offsetHeight; // flush layout
mc.style.height = "100px";
</script>

View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<title>Test for dynamic re-pagination of absolutely positioned elements</title>
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
<style>
#multicol {
height: 150px;
width: 300px;
background: yellow;
position: relative;
}
#relpos {
position: absolute;
background: aqua;
height: 150px;
width: 90px;
top: 0;
left: 0;
}
#abspos {
position: absolute;
right: 0;
height: 80px;
width: 50px;
background: blue;
}
</style>
<div id="multicol">
<div id="relpos" style="left: 0">
<div id="abspos" style="top: 80px; height: 70px"></div>
</div>
<div id="relpos" style="left: 105px; height: 100px;">
<div id="abspos" style="top: 0px; height: 10px"></div>
</div>
</div>

View File

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<title>Test for dynamic re-pagination of absolutely positioned elements</title>
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
<style>
#multicol {
columns: 3;
column-fill: auto;
column-gap: 15px;
height: 100px;
width: 300px;
background: yellow;
}
#relpos {
position: relative;
background: aqua;
height: 250px;
}
#abspos {
position: absolute;
top: 80px;
right: 0;
height: 80px;
width: 50px;
background: blue;
}
</style>
<div id="multicol">
<div id="relpos">
<div id="abspos"></div>
</div>
</div>
<script>
var mc = document.getElementById("multicol");
mc.offsetHeight; // flush layout
mc.style.height = "150px";
</script>

View File

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<title>Test for dynamic re-pagination of absolutely positioned elements</title>
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
<style>
#multicol {
height: 140px;
width: 300px;
background: yellow;
position: relative;
}
#relpos {
position: absolute;
background: aqua;
height: 140px;
width: 90px;
top: 0;
left: 0;
}
#abspos {
position: absolute;
right: 0;
height: 80px;
width: 50px;
background: blue;
}
#overflow {
position: absolute;
width: 30px;
background: grey;
}
</style>
<div id="multicol">
<div id="relpos" style="left: 0">
<div id="abspos" style="top: 60px; height: 80px">
<div id="overflow" style="height: 80px"></div>
</div>
</div>
<div id="relpos" style="left: 105px; height: 110px;">
<div id="abspos" style="top: 0px; height: 0px">
<div id="overflow" style="height: 20px"></div>
</div>
</div>
</div>

View File

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<title>Test for dynamic re-pagination of absolutely positioned elements</title>
<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com">
<style>
#multicol {
columns: 3;
column-fill: auto;
column-gap: 15px;
height: 500px;
width: 300px;
background: yellow;
}
#relpos {
position: relative;
background: aqua;
height: 250px;
}
#abspos {
position: absolute;
top: 60px;
right: 0;
height: 80px;
width: 50px;
background: blue;
}
#overflow {
height: 100px;
width: 30px;
background: grey;
}
</style>
<div id="multicol">
<div id="relpos">
<div id="abspos"><div id="overflow"></div></div>
</div>
</div>
<script>
var mc = document.getElementById("multicol");
mc.offsetHeight; // flush layout
mc.style.height = "140px";
</script>

View File

@ -13,6 +13,9 @@ pref(layout.css.box-decoration-break.enabled,true) == abspos-breaking-008.html a
pref(layout.css.box-decoration-break.enabled,true) == abspos-breaking-009.html abspos-breaking-009-ref.html
pref(layout.css.box-decoration-break.enabled,true) == abspos-breaking-010.html abspos-breaking-010-ref.html
== abspos-breaking-011.html abspos-breaking-011-ref.html
== abspos-breaking-dynamic-001.html abspos-breaking-dynamic-001-ref.html
== abspos-breaking-dynamic-002.html abspos-breaking-dynamic-002-ref.html
== abspos-breaking-dynamic-003.html abspos-breaking-dynamic-003-ref.html
== abspos-overflow-01.xhtml abspos-overflow-01.ref.xhtml
== abspos-overflow-01-cols.xhtml abspos-overflow-01-cols.ref.xhtml
== border-breaking-000-cols.xhtml border-breaking-000-cols.ref.xhtml

View File

@ -93,6 +93,10 @@ to mochitest command.
* test_value_storage.html `font-variant` [167]
* test_specified_value_serialization.html `bug-721136` [1]
* Unsupported prefixed values
* moz-prefixed gradient functions bug 1337655
* test_value_storage.html `-moz-linear-gradient` [322]
* ... `-moz-radial-gradient` [309]
* ... `-moz-repeating-` [298]
* serialization of prefixed gradient functions bug 1358710
* test_specified_value_serialization.html `-webkit-radial-gradient` [1]
* moz-prefixed intrinsic width values bug 1355402

View File

@ -1221,6 +1221,14 @@ pref("dom.send_after_paint_to_content", false);
pref("dom.min_timeout_value", 4);
// And for background windows
pref("dom.min_background_timeout_value", 1000);
// Timeout clamp in ms for tracking timeouts we clamp
// Note that this requires the privacy.trackingprotection.annotate_channels pref to be on in order to have any effect.
pref("dom.min_tracking_timeout_value", 4);
// And for background windows
// Note that this requires the privacy.trackingprotection.annotate_channels pref to be on in order to have any effect.
pref("dom.min_tracking_background_timeout_value", 10000);
// Delay in ms from document load until we start throttling tracking timeouts.
pref("dom.timeout.tracking_throttling_delay", 30000);
// Don't use new input types
pref("dom.experimental_forms", false);
@ -2793,11 +2801,7 @@ pref("layout.css.prefixes.box-sizing", true);
pref("layout.css.prefixes.font-features", true);
// Is -moz-prefixed gradient functions enabled?
#ifdef NIGHTLY_BUILD
pref("layout.css.prefixes.gradients", false);
#else
pref("layout.css.prefixes.gradients", true);
#endif
// Are webkit-prefixed properties & property-values supported?
pref("layout.css.prefixes.webkit", true);

View File

@ -2361,6 +2361,31 @@ nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
return NS_NewArrayEnumerator(aEnumerator, cookieList);
}
NS_IMETHODIMP
nsCookieService::GetSessionEnumerator(nsISimpleEnumerator **aEnumerator)
{
if (!mDBState) {
NS_WARNING("No DBState! Profile already closed?");
return NS_ERROR_NOT_AVAILABLE;
}
EnsureReadComplete();
nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
nsCookie* cookie = cookies[i];
// Filter out non-session cookies.
if (cookie->IsSession()) {
cookieList.AppendObject(cookie);
}
}
}
return NS_NewArrayEnumerator(aEnumerator, cookieList);
}
static nsresult
InitializeOriginAttributes(OriginAttributes* aAttrs,
JS::HandleValue aOriginAttributes,

View File

@ -41,6 +41,12 @@ interface nsICookieManager : nsISupports
*/
readonly attribute nsISimpleEnumerator enumerator;
/**
* Called to enumerate through each session cookie in the cookie list.
* The objects enumerated over are of type nsICookie
*/
readonly attribute nsISimpleEnumerator sessionEnumerator;
/**
* Called to remove an individual cookie from the cookie list, specified
* by host, name, and path. If the cookie cannot be found, no exception

View File

@ -156,7 +156,6 @@ jobs:
treeherder:
symbol: SM-tc(tsan)
tier: 3
run-on-projects: []
run:
spidermonkey-variant: tsan
tooltool-manifest: browser/config/tooltool-manifests/linux64/tsan.manifest

View File

@ -58563,6 +58563,36 @@
{}
]
],
"intersection-observer/observer-in-iframe.html": [
[
{}
]
],
"intersection-observer/resources/cross-origin-subframe.html": [
[
{}
]
],
"intersection-observer/resources/iframe-no-root-subframe.html": [
[
{}
]
],
"intersection-observer/resources/intersection-observer-test-utils.js": [
[
{}
]
],
"intersection-observer/resources/observer-in-iframe-subframe.html": [
[
{}
]
],
"intersection-observer/resources/timestamp-subframe.html": [
[
{}
]
],
"js/builtins/Math.maxmin.js": [
[
{}
@ -104111,6 +104141,138 @@
{}
]
],
"intersection-observer/client-rect.html": [
[
"/intersection-observer/client-rect.html",
{}
]
],
"intersection-observer/containing-block.html": [
[
"/intersection-observer/containing-block.html",
{}
]
],
"intersection-observer/cross-origin-iframe.html": [
[
"/intersection-observer/cross-origin-iframe.html",
{}
]
],
"intersection-observer/disconnect.html": [
[
"/intersection-observer/disconnect.html",
{}
]
],
"intersection-observer/display-none.html": [
[
"/intersection-observer/display-none.html",
{}
]
],
"intersection-observer/edge-inclusive-intersection.html": [
[
"/intersection-observer/edge-inclusive-intersection.html",
{}
]
],
"intersection-observer/iframe-no-root.html": [
[
"/intersection-observer/iframe-no-root.html",
{}
]
],
"intersection-observer/multiple-targets.html": [
[
"/intersection-observer/multiple-targets.html",
{}
]
],
"intersection-observer/multiple-thresholds.html": [
[
"/intersection-observer/multiple-thresholds.html",
{}
]
],
"intersection-observer/observer-attributes.html": [
[
"/intersection-observer/observer-attributes.html",
{}
]
],
"intersection-observer/observer-exceptions.html": [
[
"/intersection-observer/observer-exceptions.html",
{}
]
],
"intersection-observer/observer-without-js-reference.html": [
[
"/intersection-observer/observer-without-js-reference.html",
{}
]
],
"intersection-observer/remove-element.html": [
[
"/intersection-observer/remove-element.html",
{}
]
],
"intersection-observer/root-margin.html": [
[
"/intersection-observer/root-margin.html",
{}
]
],
"intersection-observer/same-document-no-root.html": [
[
"/intersection-observer/same-document-no-root.html",
{}
]
],
"intersection-observer/same-document-root.html": [
[
"/intersection-observer/same-document-root.html",
{}
]
],
"intersection-observer/same-document-zero-size-target.html": [
[
"/intersection-observer/same-document-zero-size-target.html",
{}
]
],
"intersection-observer/shadow-content.html": [
[
"/intersection-observer/shadow-content.html",
{}
]
],
"intersection-observer/timestamp.html": [
[
"/intersection-observer/timestamp.html",
{}
]
],
"intersection-observer/unclipped-root.html": [
[
"/intersection-observer/unclipped-root.html",
{}
]
],
"intersection-observer/zero-area-element-hidden.html": [
[
"/intersection-observer/zero-area-element-hidden.html",
{}
]
],
"intersection-observer/zero-area-element-visible.html": [
[
"/intersection-observer/zero-area-element-visible.html",
{}
]
],
"js/behaviours/SetPrototypeOf-window.html": [
[
"/js/behaviours/SetPrototypeOf-window.html",
@ -191666,6 +191828,118 @@
"4f94c4236168ed722f71d81bd957e0da72b29c71",
"support"
],
"intersection-observer/client-rect.html": [
"acec9a4f59ebee1840950cf766a45676490eef84",
"testharness"
],
"intersection-observer/containing-block.html": [
"8bdf6fa6a3ee09130981bf83728aa9f61a6ebc54",
"testharness"
],
"intersection-observer/cross-origin-iframe.html": [
"98ac8bd3447bc16e86c4b15708dcd86aa4b5ed92",
"testharness"
],
"intersection-observer/disconnect.html": [
"d8206b91756a367394ea9d444deefc0f37589427",
"testharness"
],
"intersection-observer/display-none.html": [
"3dc956e76b23ae44f2694f4b24579f896a1086b5",
"testharness"
],
"intersection-observer/edge-inclusive-intersection.html": [
"97921caeabdafb402de1a6f64fc28176eda3e6db",
"testharness"
],
"intersection-observer/iframe-no-root.html": [
"6cbea44f209e59ea0b901b0fe1cec7ac1aee0b64",
"testharness"
],
"intersection-observer/multiple-targets.html": [
"83173248b825b97063c61dd31c64d8fe7ba10aac",
"testharness"
],
"intersection-observer/multiple-thresholds.html": [
"dcfa7d005d1f94186b173b20d4f9c367abc5a77b",
"testharness"
],
"intersection-observer/observer-attributes.html": [
"69c802af77a38c450ce81c4c010588b0e4f240db",
"testharness"
],
"intersection-observer/observer-exceptions.html": [
"28ccc6905713894b43033e30949170439215bf2e",
"testharness"
],
"intersection-observer/observer-in-iframe.html": [
"3e6dcbc7aac43899186395f50649195b2ecc4c74",
"support"
],
"intersection-observer/observer-without-js-reference.html": [
"4211529e1fdd3bfb9b3b64c29b7d000b3c706408",
"testharness"
],
"intersection-observer/remove-element.html": [
"f2b3688fd8e069a41e16086baad74bda0fe2f180",
"testharness"
],
"intersection-observer/resources/cross-origin-subframe.html": [
"91073aed8b250177d69ee36e9be95c30c9cf1c95",
"support"
],
"intersection-observer/resources/iframe-no-root-subframe.html": [
"a9ea444d6bb15cda784871e2d495dc204b0e3d50",
"support"
],
"intersection-observer/resources/intersection-observer-test-utils.js": [
"889d403e334e3c0fc4ae7a6af06ef9504145fa2b",
"support"
],
"intersection-observer/resources/observer-in-iframe-subframe.html": [
"5f390b2e969132bce9ab486cbf2292765d5c62f2",
"support"
],
"intersection-observer/resources/timestamp-subframe.html": [
"7b590da7cf426cc7d2e3f25fac30f4533e36153c",
"support"
],
"intersection-observer/root-margin.html": [
"be98d804610d2c08ee516a4aecbe59ffc58c22bd",
"testharness"
],
"intersection-observer/same-document-no-root.html": [
"0f29996e4542db243c3fcd9c108463ba7260cb8e",
"testharness"
],
"intersection-observer/same-document-root.html": [
"71876c5290184f77223843c7d3c0bed670e2ee3f",
"testharness"
],
"intersection-observer/same-document-zero-size-target.html": [
"c1d1054447e116becb50aaf96aad00a25f0a6752",
"testharness"
],
"intersection-observer/shadow-content.html": [
"11681640d5c8b2c62229ed5a717172f917d75ba4",
"testharness"
],
"intersection-observer/timestamp.html": [
"cf4e91716ed1d02935c3c73ee639e566cf5b60a4",
"testharness"
],
"intersection-observer/unclipped-root.html": [
"67dab96304c745f1b5462bb1074753b09d77fbd1",
"testharness"
],
"intersection-observer/zero-area-element-hidden.html": [
"59d854e89ca0d7b035a87376566775ca2f3420e5",
"testharness"
],
"intersection-observer/zero-area-element-visible.html": [
"f3e1fbeb1a912be412724cec47a6aa981664ff7d",
"testharness"
],
"js/behaviours/SetPrototypeOf-window.html": [
"92efe1a4f3910a32097fb3cbeef0019d82a0e78a",
"testharness"

View File

@ -0,0 +1 @@
prefs: [dom.IntersectionObserver.enabled:true]

View File

@ -0,0 +1,2 @@
[client-rect.html]
type: testharness

View File

@ -0,0 +1,3 @@
[containing-block.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1363650

View File

@ -0,0 +1,3 @@
[cross-origin-iframe.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,3 @@
[disconnect.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,3 @@
[display-none.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1358668

View File

@ -0,0 +1,5 @@
[edge-inclusive-intersection.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1359317
[First rAF.]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,5 @@
[iframe-no-root.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1359318
[First rAF.]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,5 @@
[multiple-targets.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1359311
[First rAF.]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,3 @@
[multiple-thresholds.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,2 @@
[observer-attributes.html]
type: testharness

View File

@ -0,0 +1,2 @@
[observer-exceptions.html]
type: testharness

View File

@ -0,0 +1,2 @@
[observer-in-iframe.html]
type: testharness

View File

@ -0,0 +1,3 @@
[observer-without-js-reference.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,5 @@
[remove-element.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1363650
[First rAF.]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,3 @@
[root-margin.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,3 @@
[same-document-no-root.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,5 @@
[same-document-root.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1363650
[First rAF.]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,3 @@
[same-document-zero-size-target.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,4 @@
[shadow-content.html]
type: testharness
prefs: [dom.webcomponents.enabled:true]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1359318

View File

@ -0,0 +1,3 @@
[timestamp.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,5 @@
[unclipped-root.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1359317
[First rAF.]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,3 @@
[zero-area-element-hidden.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1,5 @@
[zero-area-element-visible.html]
type: testharness
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1359316
[First rAF should generate a notification.]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1335644

View File

@ -0,0 +1 @@
prefs: [dom.enable_performance_observer:true]

View File

@ -1,6 +1,5 @@
[storagemanager-persist.https.html]
type: testharness
expected: TIMEOUT
[navigator.storage.persist() returns a promise that resolves.]
expected: TIMEOUT
prefs: [browser.storageManager.enabled:true,
dom.storageManager.prompt.testing:true]

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
iframe {
width: 180px;
height: 100px;
}
</style>
<iframe id="iframe" srcdoc="<div id='target' style='width:1000px;height:1000px;'></div>"></iframe>
<script>
var target;
var entries = [];
var observer;
var iframe = document.getElementById("iframe");
iframe.onload = function() {
runTestCycle(function() {
target = iframe.contentDocument.getElementById("target");
assert_true(!!target, "Target element exists.");
observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes);
});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(test0, "First rAF should generate notification.");
}, "IntersectionObserverEntry.boundingClientRect should match target.boundingClientRect()");
};
function test0() {
assert_equals(entries.length, 1, "One notification.");
var bcr = target.getBoundingClientRect();
checkLastEntry(entries, 0, [bcr.left, bcr.right, bcr.top, bcr.bottom]);
}
</script>

View File

@ -0,0 +1,73 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
#root {
width: 170px;
height: 200px;
overflow-y: scroll;
}
#target {
width: 100px;
height: 100px;
background-color: green;
position: absolute;
}
</style>
<div id="root" style="position: absolute">
<div id="target" style="left: 50px; top: 250px"></div>
</div>
<script>
var entries = [];
var root, target;
runTestCycle(function() {
root = document.getElementById("root");
assert_true(!!root, "root element exists.");
target = document.getElementById("target");
assert_true(!!target, "target element exists.");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes);
}, { root: root });
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
target.style.top = "10px";
runTestCycle(test1, "In containing block and intersecting.");
}, "IntersectionObserver should only report intersections if root is a containing block ancestor of target.");
function test1() {
runTestCycle(test2, "In containing block and not intersecting.");
var rootBounds = contentBounds(root);
checkLastEntry(entries, 0, [58, 158, 18, 118, 58, 158, 18, 118].concat(rootBounds));
target.style.top = "250px";
}
function test2() {
runTestCycle(test3, "Not in containing block and intersecting.");
var rootBounds = contentBounds(root);
checkLastEntry(entries, 1, [58, 158, 258, 358, 0, 0, 0, 0].concat(rootBounds));
root.style.position = "static";
target.style.top = "10px";
}
function test3() {
runTestCycle(test4, "Not in containing block and not intersecting.");
checkLastEntry(entries, 1);
target.style.top = "250px";
}
function test4() {
checkLastEntry(entries, 1);
target.style.top = "0";
}
</script>

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
iframe {
width: 160px;
height: 100px;
overflow-y: scroll;
}
.spacer {
height: calc(100vh + 100px);
}
</style>
<div class="spacer"></div>
<iframe src="resources/cross-origin-subframe.html" sandbox="allow-scripts"></iframe>
<div class="spacer"></div>
<script>
async_test(function(t) {
var iframe = document.querySelector("iframe");
function handleMessage(event) {
if (event.data.hasOwnProperty('scrollTo')) {
document.scrollingElement.scrollTop = event.data.scrollTo;
waitForNotification(t, function() { iframe.contentWindow.postMessage("", "*"); },
"document.scrollingElement.scrollTop = " + event.data.scrollTo);
} else if (event.data.hasOwnProperty('actual')) {
checkJsonEntries(event.data.actual, event.data.expected, event.data.description);
} else if (event.data.hasOwnProperty('DONE')) {
document.scrollingElement.scrollTop = 0;
t.done();
} else {
var description = event.data.description;
waitForNotification(t, function() { iframe.contentWindow.postMessage("", "*"); }, description);
}
}
window.addEventListener("message", t.step_func(handleMessage));
iframe.onload = t.step_func(function() {
waitForNotification(t, function() { iframe.contentWindow.postMessage("", "*") }, "setup");
});
}, "Intersection observer test with no explicit root and target in a cross-origin iframe.");
</script>

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
.spacer {
height: calc(100vh + 100px);
}
#target {
width: 100px;
height: 100px;
background-color: green;
}
</style>
<div class="spacer"></div>
<div id="target"></div>
<div class="spacer"></div>
<script>
var entries = [];
var observer;
var target;
runTestCycle(function() {
target = document.getElementById("target");
assert_true(!!target, "target exists");
observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
}, "IntersectionObserver should not deliver pending notifications after disconnect().");
function step0() {
runTestCycle(step1, "observer.disconnect()");
document.scrollingElement.scrollTop = 300;
observer.disconnect();
assert_equals(entries.length, 1, "Initial notification.");
}
function step1() {
assert_equals(entries.length, 1, "No new notifications.");
}
</script>

View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
#target {
background-color: green;
width: 100px;
height: 100px;
}
</style>
<div id="target"></div>
<script>
var vw = document.documentElement.clientWidth;
var vh = document.documentElement.clientHeight;
var entries = [];
runTestCycle(function() {
var target = document.getElementById("target");
var root = document.getElementById("root");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "Intersecting notification after first rAF.");
}, "IntersectionObserver should send a not-intersecting notification for a target that gets display:none.");
function step0() {
runTestCycle(step1, "Not-intersecting notification after setting display:none on target.");
checkLastEntry(entries, 0, [8, 108, 8, 108, 8, 108, 8, 108, 0, vw, 0, vh, true]);
target.style.display = "none";
}
function step1() {
runTestCycle(step2, "Intersecting notification after removing display:none on target.");
checkLastEntry(entries, 1, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false]);
target.style.display = "";
}
function step2() {
checkLastEntry(entries, 2, [8, 108, 8, 108, 8, 108, 8, 108, 0, vw, 0, vh, true]);
}
</script>

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
#root {
width: 200px;
height: 200px;
overflow: visible;
}
#target {
background-color: green;
}
</style>
<div id="root">
<div id="target" style="width: 100px; height: 100px; transform: translateY(250px)"></div>
</div>
<script>
var entries = [];
runTestCycle(function() {
var root = document.getElementById('root');
assert_true(!!root, "root element exists.");
var target = document.getElementById('target');
assert_true(!!target, "target element exists.");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes);
}, { root: root });
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
}, "IntersectionObserver should detect and report edge-adjacent and zero-area intersections.");
function step0() {
runTestCycle(step1, "Set transform=translateY(200px) on target.");
checkLastEntry(entries, 0, [8, 108, 258, 358, 0, 0, 0, 0, 8, 208, 8, 208, false]);
target.style.transform = "translateY(200px)";
}
function step1() {
runTestCycle(step2, "Set transform=translateY(201px) on target.");
checkLastEntry(entries, 1, [8, 108, 208, 308, 8, 108, 208, 208, 8, 208, 8, 208, true]);
target.style.transform = "translateY(201px)";
}
function step2() {
runTestCycle(step3, "Set transform=translateY(185px) on target.");
checkLastEntry(entries, 2);
target.style.height = "0px";
target.style.width = "300px";
target.style.transform = "translateY(185px)";
}
function step3() {
checkLastEntry(entries, 3, [8, 308, 193, 193, 8, 208, 193, 193, 8, 208, 8, 208, true]);
}
</script>

View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
.spacer {
height: calc(100vh + 100px);
}
iframe {
height: 100px;
width: 150px;
}
</style>
<div class="spacer"></div>
<iframe id="target-iframe" src="resources/iframe-no-root-subframe.html"></iframe>
<div class="spacer"></div>
<script>
var vw = document.documentElement.clientWidth;
var vh = document.documentElement.clientHeight;
var iframe = document.getElementById("target-iframe");
var target;
var entries = [];
iframe.onload = function() {
runTestCycle(function() {
assert_true(!!iframe, "iframe exists");
target = iframe.contentDocument.getElementById("target");
assert_true(!!target, "Target element exists.");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
}, "Observer with the implicit root; target in a same-origin iframe.");
};
function step0() {
document.scrollingElement.scrollTop = 200;
runTestCycle(step1, "document.scrollingElement.scrollTop = 200");
checkLastEntry(entries, 0, [8, 108, 208, 308, 0, 0, 0, 0, 0, vw, 0, vh, false]);
}
function step1() {
iframe.contentDocument.scrollingElement.scrollTop = 250;
runTestCycle(step2, "iframe.contentDocument.scrollingElement.scrollTop = 250");
assert_equals(entries.length, 1, "entries.length == 1");
}
function step2() {
document.scrollingElement.scrollTop = 100;
runTestCycle(step3, "document.scrollingElement.scrollTop = 100");
checkLastEntry(entries, 1, [8, 108, -42, 58, 8, 108, 0, 58, 0, vw, 0, vh, true]);
}
function step3() {
checkLastEntry(entries, 2, [8, 108, -42, 58, 0, 0, 0, 0, 0, vw, 0, vh, false]);
document.scrollingElement.scrollTop = 0;
}
</script>

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
.spacer {
height: calc(100vh + 100px);
}
.target {
width: 100px;
height: 100px;
margin: 10px;
background-color: green;
}
</style>
<div class="spacer"></div>
<div id="target1" class="target"></div>
<div id="target2" class="target"></div>
<div id="target3" class="target"></div>
<script>
var entries = [];
var target1, target2, target3;
runTestCycle(function() {
target1 = document.getElementById("target1");
assert_true(!!target1, "target1 exists.");
target2 = document.getElementById("target2");
assert_true(!!target2, "target2 exists.");
target3 = document.getElementById("target3");
assert_true(!!target3, "target3 exists.");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
});
observer.observe(target1);
observer.observe(target2);
observer.observe(target3);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
}, "One observer with multiple targets.");
function step0() {
document.scrollingElement.scrollTop = 150;
runTestCycle(step1, "document.scrollingElement.scrollTop = 150");
assert_equals(entries.length, 3, "Three initial notifications.");
assert_equals(entries[0].target, target1, "entries[0].target === target1");
assert_equals(entries[1].target, target2, "entries[1].target === target2");
assert_equals(entries[2].target, target3, "entries[2].target === target3");
}
function step1() {
document.scrollingElement.scrollTop = 10000;
runTestCycle(step2, "document.scrollingElement.scrollTop = 10000");
assert_equals(entries.length, 4, "Four notifications.");
assert_equals(entries[3].target, target1, "entries[3].target === target1");
}
function step2() {
document.scrollingElement.scrollTop = 0;
runTestCycle(step3, "document.scrollingElement.scrollTop = 0");
assert_equals(entries.length, 6, "Six notifications.");
assert_equals(entries[4].target, target2, "entries[4].target === target2");
assert_equals(entries[5].target, target3, "entries[5].target === target3");
}
function step3() {
assert_equals(entries.length, 9, "Nine notifications.");
assert_equals(entries[6].target, target1, "entries[6].target === target1");
assert_equals(entries[7].target, target2, "entries[7].target === target2");
assert_equals(entries[8].target, target3, "entries[8].target === target3");
}
</script>

View File

@ -0,0 +1,96 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
.spacer {
height: calc(100vh + 100px);
}
#target {
width: 100px;
height: 100px;
background-color: green;
}
</style>
<div class="spacer"></div>
<div id="target"></div>
<div class="spacer"></div>
<script>
var vw = document.documentElement.clientWidth;
var vh = document.documentElement.clientHeight;
var entries = [];
var target;
runTestCycle(function() {
target = document.getElementById("target");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
}, { threshold: [0, 0.25, 0.5, 0.75, 1] });
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
}, "Observer with multiple thresholds.");
function step0() {
document.scrollingElement.scrollTop = 120;
runTestCycle(step1, "document.scrollingElement.scrollTop = 120");
checkLastEntry(entries, 0, [8, 108, vh + 108, vh + 208, 0, 0, 0, 0, 0, vw, 0, vh, false]);
}
function step1() {
document.scrollingElement.scrollTop = 160;
runTestCycle(step2, "document.scrollingElement.scrollTop = 160");
checkLastEntry(entries, 1, [8, 108, vh - 12, vh + 88, 8, 108, vh - 12, vh, 0, vw, 0, vh, true]);
}
function step2() {
document.scrollingElement.scrollTop = 200;
runTestCycle(step3, "document.scrollingElement.scrollTop = 200");
checkLastEntry(entries, 2, [8, 108, vh - 52, vh + 48, 8, 108, vh - 52, vh, 0, vw, 0, vh, true]);
}
function step3() {
document.scrollingElement.scrollTop = 240;
runTestCycle(step4, "document.scrollingElement.scrollTop = 240");
checkLastEntry(entries, 3, [8, 108, vh - 92, vh + 8, 8, 108, vh - 92, vh, 0, vw, 0, vh, true]);
}
function step4() {
document.scrollingElement.scrollTop = vh + 140;
runTestCycle(step5, "document.scrollingElement.scrollTop = window.innerHeight + 140");
checkLastEntry(entries, 4, [8, 108, vh - 132, vh - 32, 8, 108, vh - 132, vh - 32, 0, vw, 0, vh, true]);
}
function step5() {
document.scrollingElement.scrollTop = vh + 160;
runTestCycle(step6, "document.scrollingElement.scrollTop = window.innerHeight + 160");
checkLastEntry(entries, 5, [8, 108, -32, 68, 8, 108, 0, 68, 0, vw, 0, vh, true]);
}
function step6() {
document.scrollingElement.scrollTop = vh + 200;
runTestCycle(step7, "document.scrollingElement.scrollTop = window.innerHeight + 200");
checkLastEntry(entries, 6, [8, 108, -52, 48, 8, 108, 0, 48, 0, vw, 0, vh, true]);
}
function step7() {
checkLastEntry(entries, 7, [8, 108, -92, 8, 8, 108, 0, 8, 0, vw, 0, vh, true]);
document.scrollingElement.scrollTop = vh + 220;
runTestCycle(step8, "document.scrollingElement.scrollTop = window.innerHeight + 220");
}
function step8() {
checkLastEntry(entries, 8, [8, 108, -112, -12, 0, 0, 0, 0, 0, vw, 0, vh, false]);
document.scrollingElement.scrollTop = 0;
}
</script>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="root"></div>
<script>
test(function() {
var observer = new IntersectionObserver(function(e) {}, {});
test(function() { assert_equals(observer.root, null) },
"observer.root");
test(function() { assert_array_equals(observer.thresholds, [0]) },
"observer.thresholds");
test(function() { assert_equals(observer.rootMargin, "0px 0px 0px 0px") },
"observer.rootMargin");
var rootDiv = document.getElementById("root");
observer = new IntersectionObserver(function(e) {}, {
root: rootDiv,
threshold: [0, 0.25, 0.5, 1.0],
rootMargin: "10% 20px"
});
test(function() { assert_equals(observer.root, rootDiv) },
"set observer.root");
test(function() { assert_array_equals(observer.thresholds, [0, 0.25, 0.5, 1.0]) },
"set observer.thresholds");
test(function() { assert_equals(observer.rootMargin, "10% 20px 10% 20px") },
"set observer.rootMargin");
}, "Observer attribute getters.");
</script>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
test(function () {
assert_throws(RangeError(), function() {
new IntersectionObserver(e => {}, {threshold: [1.1]})
})
}, "IntersectionObserver constructor with { threshold: [1.1] }");
test(function () {
assert_throws(TypeError(), function() {
new IntersectionObserver(e => {}, {threshold: ["foo"]})
})
}, 'IntersectionObserver constructor with { threshold: ["foo"] }');
test(function () {
assert_throws("SYNTAX_ERR", function() {
new IntersectionObserver(e => {}, {rootMargin: "1"})
})
}, 'IntersectionObserver constructor witth { rootMargin: "1" }');
test(function () {
assert_throws("SYNTAX_ERR", function() {
new IntersectionObserver(e => {}, {rootMargin: "2em"})
})
}, 'IntersectionObserver constructor with { rootMargin: "2em" }');
test(function () {
assert_throws("SYNTAX_ERR", function() {
new IntersectionObserver(e => {}, {rootMargin: "auto"})
})
}, 'IntersectionObserver constructor width { rootMargin: "auto" }');
test(function () {
assert_throws("SYNTAX_ERR", function() {
new IntersectionObserver(e => {}, {rootMargin: "1px 1px 1px 1px 1px"})
})
}, 'IntersectionObserver constructor with { rootMargin: "1px 1px 1px 1px 1px" }');
test(function () {
assert_throws(TypeError(), function() {
let observer = new IntersectionObserver(c => {}, {});
observer.observe("foo");
})
}, 'IntersectionObserver.observe("foo")');
</script>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
</style>
<iframe id="target-iframe" src="resources/observer-in-iframe-subframe.html" width="150px" height="150px"></iframe>

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
.spacer {
height: calc(100vh + 100px);
}
#target {
width: 100px;
height: 100px;
background-color: green;
}
</style>
<div class="spacer"></div>
<div id="target"></div>
<div class="spacer"></div>
<script>
var entries = [];
runTestCycle(function() {
var target = document.getElementById("target");
assert_true(!!target, "Target exists");
function createObserver() {
new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
}).observe(target);
}
createObserver();
runTestCycle(step0, "First rAF");
}, "IntersectionObserver that is unreachable in js should still generate notifications.");
function step0() {
document.scrollingElement.scrollTop = 300;
runTestCycle(step1, "document.scrollingElement.scrollTop = 300");
assert_equals(entries.length, 1, "One notification.");
}
function step1() {
document.scrollingElement.scrollTop = 0;
assert_equals(entries.length, 2, "Two notifications.");
}
</script>

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
#root {
display: inline-block;
overflow-y: scroll;
height: 200px;
border: 3px solid black;
}
#target {
width: 100px;
height: 100px;
background-color: green;
}
.spacer {
height: 300px;
}
</style>
<div id="root">
<div id="leading-space" class="spacer"></div>
<div id="target"></div>
<div id="trailing-space" class="spacer"</div>
</div>
<script>
var entries = [];
var root, target, trailingSpace;
runTestCycle(function() {
target = document.getElementById("target");
assert_true(!!target, "Target exists");
trailingSpace = document.getElementById("trailing-space");
assert_true(!!trailingSpace, "TrailingSpace exists");
root = document.getElementById("root");
assert_true(!!root, "Root exists");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
}, {root: root});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF");
}, "Verify that not-intersecting notifications are sent when a target is removed from the DOM tree.");
function step0() {
root.scrollTop = 150;
runTestCycle(step1, "root.scrollTop = 150");
checkLastEntry(entries, 0, [11, 111, 311, 411, 0, 0, 0, 0, 11, 111, 11, 211, false]);
}
function step1() {
root.removeChild(target);
runTestCycle(step2, "root.removeChild(target).");
checkLastEntry(entries, 1, [11, 111, 161, 261, 11, 111, 161, 211, 11, 111, 11, 211, true]);
}
function step2() {
root.scrollTop = 0;
root.insertBefore(target, trailingSpace);
runTestCycle(step3, "root.insertBefore(target, trailingSpace).");
checkLastEntry(entries, 2, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false]);
}
function step3() {
root.scrollTop = 150;
runTestCycle(step4, "root.scrollTop = 150 after reinserting target.");
checkLastEntry(entries, 2);
}
function step4() {
checkLastEntry(entries, 3, [11, 111, 161, 261, 11, 111, 161, 211, 11, 111, 11, 211, true]);
}
</script>

View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<div style="height: 200px; width: 100px;"></div>
<div id="target" style="background-color: green; width:100px; height:100px"></div>
<div style="height: 200px; width: 100px;"></div>
<script>
var port;
var entries = [];
var target = document.getElementById('target');
var scroller = document.scrollingElement;
var nextStep;
function clientRectToJson(rect) {
if (!rect)
return "null";
return {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left
};
}
function entryToJson(entry) {
return {
boundingClientRect: clientRectToJson(entry.boundingClientRect),
intersectionRect: clientRectToJson(entry.intersectionRect),
rootBounds: clientRectToJson(entry.rootBounds),
target: entry.target.id
};
}
function coordinatesToClientRectJson(top, right, bottom, left) {
return {
top: top,
right: right,
bottom: bottom,
left: left
};
}
// Note that we never use RAF in this code, because this frame might get render-throttled.
// Instead of RAF-ing, we just post an empty message to the parent window, which will
// RAF when it is received, and then send us a message to cause the next step to run.
// Use a rootMargin here, and verify it does NOT get applied for the cross-origin case.
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
}, { rootMargin: "7px" });
observer.observe(target);
function step0() {
entries = entries.concat(observer.takeRecords());
nextStep = step1;
var expected = [{
boundingClientRect: coordinatesToClientRectJson(8, 208, 108, 308),
intersectionRect: coordinatesToClientRectJson(0, 0, 0, 0),
rootBounds: "null",
target: target.id
}];
port.postMessage({
actual: entries.map(entryToJson),
expected: expected,
description: "First rAF"
}, "*");
entries = [];
port.postMessage({scrollTo: 200}, "*");
}
function step1() {
entries = entries.concat(observer.takeRecords());
port.postMessage({
actual: entries.map(entryToJson),
expected: [],
description: "topDocument.scrollingElement.scrollTop = 200"
}, "*");
entries = [];
scroller.scrollTop = 250;
nextStep = step2;
port.postMessage({}, "*");
}
function step2() {
entries = entries.concat(observer.takeRecords());
var expected = [{
boundingClientRect: coordinatesToClientRectJson(-42, 108, 58, 8),
intersectionRect: coordinatesToClientRectJson(0, 108, 58, 8),
rootBounds: "null",
target: target.id
}];
port.postMessage({
actual: entries.map(entryToJson),
expected: expected,
description: "iframeDocument.scrollingElement.scrollTop = 250"
}, "*");
entries = [];
nextStep = step3;
port.postMessage({scrollTo: 100}, "*");
}
function step3() {
entries = entries.concat(observer.takeRecords());
var expected = [{
boundingClientRect: coordinatesToClientRectJson(-42, 108, 58, 8),
intersectionRect: coordinatesToClientRectJson(0, 0, 0, 0),
rootBounds: "null",
target: target.id
}];
port.postMessage({
actual: entries.map(entryToJson),
expected: expected,
description: "topDocument.scrollingElement.scrollTop = 100"
}, "*");
port.postMessage({DONE: 1}, "*");
}
function handleMessage(event)
{
port = event.source;
nextStep();
}
nextStep = step0;
window.addEventListener("message", handleMessage);
</script>

View File

@ -0,0 +1,4 @@
<!DOCTYPE html>
<div style="height: 200px; width: 100px;"></div>
<div id="target" style="background-color: green; width:100px; height:100px"></div>
<div style="height: 200px; width: 100px;"></div>

View File

@ -0,0 +1,122 @@
// Here's how waitForNotification works:
//
// - myTestFunction0()
// - waitForNotification(myTestFunction1)
// - requestAnimationFrame()
// - Modify DOM in a way that should trigger an IntersectionObserver callback.
// - BeginFrame
// - requestAnimationFrame handler runs
// - First step_timeout()
// - Style, layout, paint
// - IntersectionObserver generates new notifications
// - Posts a task to deliver notifications
// - First step_timeout handler runs
// - Second step_timeout()
// - Task to deliver IntersectionObserver notifications runs
// - IntersectionObserver callbacks run
// - Second step_timeout handler runs
// - myTestFunction1()
// - [optional] waitForNotification(myTestFunction2)
// - requestAnimationFrame()
// - Verify newly-arrived IntersectionObserver notifications
// - [optional] Modify DOM to trigger new notifications
function waitForNotification(t, f) {
requestAnimationFrame(function() {
t.step_timeout(function() { t.step_timeout(f); });
});
}
// The timing of when runTestCycle is called is important. It should be
// called:
//
// - Before or during the window load event, or
// - Inside of a prior runTestCycle callback, *before* any assert_* methods
// are called.
//
// Following these rules will ensure that the test suite will not abort before
// all test steps have run.
function runTestCycle(f, description) {
async_test(function(t) {
waitForNotification(t, t.step_func_done(f));
}, description);
}
// Root bounds for a root with an overflow clip as defined by:
// http://wicg.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle
function contentBounds(root) {
var left = root.offsetLeft + root.clientLeft;
var right = left + root.clientWidth;
var top = root.offsetTop + root.clientTop;
var bottom = top + root.clientHeight;
return [left, right, top, bottom];
}
// Root bounds for a root without an overflow clip as defined by:
// http://wicg.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle
function borderBoxBounds(root) {
var left = root.offsetLeft;
var right = left + root.offsetWidth;
var top = root.offsetTop;
var bottom = top + root.offsetHeight;
return [left, right, top, bottom];
}
function clientBounds(element) {
var rect = element.getBoundingClientRect();
return [rect.left, rect.right, rect.top, rect.bottom];
}
function rectArea(rect) {
return (rect.left - rect.right) * (rect.bottom - rect.top);
}
function checkRect(actual, expected, description, all) {
if (!expected.length)
return;
assert_equals(actual.left | 0, expected[0] | 0, description + '.left');
assert_equals(actual.right | 0, expected[1] | 0, description + '.right');
assert_equals(actual.top | 0, expected[2] | 0, description + '.top');
assert_equals(actual.bottom | 0, expected[3] | 0, description + '.bottom');
}
function checkLastEntry(entries, i, expected) {
assert_equals(entries.length, i + 1, 'entries.length');
if (expected) {
checkRect(
entries[i].boundingClientRect, expected.slice(0, 4),
'entries[' + i + '].boundingClientRect', entries[i]);
checkRect(
entries[i].intersectionRect, expected.slice(4, 8),
'entries[' + i + '].intersectionRect', entries[i]);
checkRect(
entries[i].rootBounds, expected.slice(8, 12),
'entries[' + i + '].rootBounds', entries[i]);
if (expected.length > 12) {
assert_equals(
entries[i].isIntersecting, expected[12],
'entries[' + i + '].isIntersecting');
}
}
}
function checkJsonEntry(actual, expected) {
checkRect(
actual.boundingClientRect, expected.boundingClientRect,
'entry.boundingClientRect');
checkRect(
actual.intersectionRect, expected.intersectionRect,
'entry.intersectionRect');
if (actual.rootBounds == 'null')
assert_equals(expected.rootBounds, 'null', 'rootBounds is null');
else
checkRect(actual.rootBounds, expected.rootBounds, 'entry.rootBounds');
assert_equals(actual.target, expected.target);
}
function checkJsonEntries(actual, expected, description) {
test(function() {
assert_equals(actual.length, expected.length);
for (var i = 0; i < actual.length; i++)
checkJsonEntry(actual[i], expected[i]);
}, description);
}

View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./intersection-observer-test-utils.js"></script>
<style>
#root {
width: 200px;
height: 200px;
}
#scroller {
width: 160px;
height: 200px;
overflow-y: scroll;
}
#target {
width: 100px;
height: 100px;
background-color: green;
}
.spacer {
height: 300px;
}
</style>
<div id="root">
<div id="scroller">
<div class="spacer"></div>
<div id="target"></div>
<div class="spacer"></div>
</div>
</div>
<script>
setup({message_events: [], output_document: window.parent.document});
var entries = [];
var root, scroller, target;
runTestCycle(function() {
root = document.getElementById("root");
assert_true(!!root, "Root element exists.");
scroller = document.getElementById("scroller");
assert_true(!!scroller, "Scroller element exists.");
target = document.getElementById("target");
assert_true(!!target, "Target element exists.");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
}, {root: root});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.")
runTestCycle(step1, "First rAF.");
}, "IntersectionObserver in iframe with explicit root.");
function step1() {
scroller.scrollTop = 250;
runTestCycle(step2, "scroller.scrollTop = 250");
checkLastEntry(entries, 0, [8, 108, 308, 408, 0, 0, 0, 0, 8, 208, 8, 208, false]);
}
function step2() {
checkLastEntry(entries, 1, [8, 108, 58, 158, 8, 108, 58, 158, 8, 208, 8, 208, true]);
}
</script>

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<style>
#target {
width: 100px;
height: 100px;
background-color: green;
}
.spacer {
width: height: 100px
}
</style>
<div class="spacer"></div>
<div id="target"></div>
<div class="spacer"></div>
<script>
document.createObserverCallback = function(entries) {
return function(newEntries) {
for (var i in newEntries) {
entries.push(newEntries[i]);
}
};
}
document.createObserver = function(callback) {
return new IntersectionObserver(callback, {});
};
</script>

View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
#target {
display: inline-block;
width: 100px;
height: 100px;
background-color: green;
}
.vertical-spacer {
height: calc(100vh + 100px);
}
.horizontal-spacer {
display: inline-block;
width: 120vw;
}
</style>
<div class="vertical-spacer"></div>
<div style="white-space:nowrap;">
<div class="horizontal-spacer"></div>
<div id="target"></div>
<div class="horizontal-spacer"></div>
</div>
<div class="vertical-spacer"></div>
<script>
var vw = document.documentElement.clientWidth;
var vh = document.documentElement.clientHeight;
var entries = [];
var target;
runTestCycle(function() {
target = document.getElementById("target");
assert_true(!!target, "Target exists");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
}, { rootMargin: "10px 20% 40% 30px" });
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
}, "Root margin tests");
function step0() {
var targetBounds = clientBounds(target);
document.scrollingElement.scrollLeft = 100;
runTestCycle(step1, "document.scrollingElement.scrollLeft = 100");
checkLastEntry(entries, 0, targetBounds.concat(0, 0, 0, 0, -30, vw * 1.2, -10, vh * 1.4, false));
}
function step1() {
var targetBounds = clientBounds(target);
var sw = window.innerWidth - document.documentElement.clientWidth;
var sh = window.innerHeight - document.documentElement.clientHeight;
document.scrollingElement.scrollTop = vh + 200;
runTestCycle(step2, "document.scrollingElement.scrollTop = document.documentElement.clientHeight + 200");
checkLastEntry(entries, 1, targetBounds.concat(
targetBounds[0], Math.min(targetBounds[1], vw * 1.2), vh + 108 + sh, Math.min(vh + 208 + sw, vh * 1.4),
-30, vw * 1.2, -10, vh * 1.4,
true
));
}
function step2() {
document.scrollingElement.scrollTop = vh + 300;
runTestCycle(step3, "document.scrollingElement.scrollTop = document.documentElement.clientHeight + 300");
checkLastEntry(entries, 1);
}
function step3() {
var targetBounds = clientBounds(target);
document.scrollingElement.scrollLeft = 0;
document.scrollingElement.scrollTop = 0;
checkLastEntry(entries, 2, targetBounds.concat(0, 0, 0, 0, -30, vw * 1.2, -10, vh * 1.4, false));
}
</script>

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
.spacer {
height: calc(100vh + 100px);
}
#target {
width: 100px;
height: 100px;
background-color: green;
}
</style>
<div class="spacer"></div>
<div id="target"></div>
<div class="spacer"></div>
<script>
var vw = document.documentElement.clientWidth;
var vh = document.documentElement.clientHeight;
var entries = [];
var target;
runTestCycle(function() {
target = document.getElementById("target");
assert_true(!!target, "target exists");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
}, "IntersectionObserver in a single document using the implicit root.");
function step0() {
document.scrollingElement.scrollTop = 300;
runTestCycle(step1, "document.scrollingElement.scrollTop = 300");
checkLastEntry(entries, 0, [8, 108, vh + 108, vh + 208, 0, 0, 0, 0, 0, vw, 0, vh, false]);
}
function step1() {
document.scrollingElement.scrollTop = 100;
runTestCycle(step2, "document.scrollingElement.scrollTop = 100");
checkLastEntry(entries, 1, [8, 108, vh - 192, vh - 92, 8, 108, vh - 192, vh - 92, 0, vw, 0, vh, true]);
}
function step2() {
document.scrollingElement.scrollTop = 0;
checkLastEntry(entries, 2, [8, 108, vh + 8, vh + 108, 0, 0, 0, 0, 0, vw, 0, vh, false]);
}
</script>

View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
.spacer {
height: calc(100vh + 100px);
}
#root {
display: inline-block;
overflow-y: scroll;
height: 200px;
border: 3px solid black;
}
#target {
width: 100px;
height: 100px;
background-color: green;
}
</style>
<div class="spacer"></div>
<div id="root">
<div style="height: 300px;"></div>
<div id="target"></div>
</div>
<div class="spacer"></div>
<script>
var vw = document.documentElement.clientWidth;
var vh = document.documentElement.clientHeight;
var entries = [];
var root, target;
runTestCycle(function() {
target = document.getElementById("target");
assert_true(!!target, "target exists");
root = document.getElementById("root");
assert_true(!!root, "root exists");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
}, { root: root });
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF");
}, "IntersectionObserver in a single document with explicit root.");
function step0() {
document.scrollingElement.scrollTop = vh;
runTestCycle(step1, "document.scrollingElement.scrollTop = window.innerHeight.");
checkLastEntry(entries, 0, [ 11, 111, vh + 411, vh + 511, 0, 0, 0, 0, 11, 111, vh + 111, vh + 311, false]);
}
function step1() {
root.scrollTop = 150;
runTestCycle(step2, "root.scrollTop = 150 with root scrolled into view.");
assert_equals(entries.length, 1, "No notifications after scrolling frame.");
}
function step2() {
document.scrollingElement.scrollTop = 0;
runTestCycle(step3, "document.scrollingElement.scrollTop = 0.");
checkLastEntry(entries, 1, [11, 111, 261, 361, 11, 111, 261, 311, 11, 111, 111, 311, true]);
}
function step3() {
root.scrollTop = 0;
runTestCycle(step4, "root.scrollTop = 0");
checkLastEntry(entries, 1);
}
function step4() {
root.scrollTop = 150;
runTestCycle(step5, "root.scrollTop = 150 with root scrolled out of view.");
checkLastEntry(entries, 2, [11, 111, vh + 411, vh + 511, 0, 0, 0, 0, 11, 111, vh + 111, vh + 311, false]);
}
// This tests that notifications are generated even when the root element is off screen.
function step5() {
checkLastEntry(entries, 3, [11, 111, vh + 261, vh + 361, 11, 111, vh + 261, vh + 311, 11, 111, vh + 111, vh + 311, true]);
}
</script>

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
.spacer {
height: calc(100vh + 100px);
}
#target {
width: 0px;
height: 0px;
background-color: green;
}
</style>
<div class="spacer"></div>
<div id="target"></div>
<div class="spacer"></div>
<script>
var vw = document.documentElement.clientWidth;
var vh = document.documentElement.clientHeight;
var entries = [];
var target;
runTestCycle(function() {
target = document.getElementById("target");
assert_true(!!target, "Target exists");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF");
}, "Observing a zero-area target.");
function step0() {
document.scrollingElement.scrollTop = 300;
runTestCycle(step1, "document.scrollingElement.scrollTop = 300");
checkLastEntry(entries, 0, [8, 8, vh + 108, vh + 108, 0, 0, 0, 0, 0, vw, 0, vh, false]);
}
function step1() {
document.scrollingElement.scrollTop = 100;
runTestCycle(step2, "document.scrollingElement.scrollTop = 100");
checkLastEntry(entries, 1, [8, 8, vh - 192, vh - 192, 8, 8, vh - 192, vh - 192, 0, vw, 0, vh, true]);
}
function step2() {
document.scrollingElement.scrollTop = 0;
checkLastEntry(entries, 2, [8, 8, vh + 8, vh + 8, 0, 0, 0, 0, 0, vw, 0, vh, false]);
}
</script>

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
</style>
<div id="host"></div>
<script>
var vw = document.documentElement.clientWidth;
var vh = document.documentElement.clientHeight;
var entries = [];
var target;
runTestCycle(function() {
var shadowHost = document.getElementById("host");
assert_true(!!shadowHost, "Host exists");
var shadowRoot = shadowHost.createShadowRoot();
assert_true(!!shadowRoot, "Shadow root exists");
shadowRoot.innerHTML = "<div id='target' style='width: 100px; height: 100px; background-color: green;'></div>";
target = shadowRoot.getElementById("target");
assert_true(!!target, "target exists");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF after creating shadow DOM.");
}, "Observing a target inside shadow DOM.");
function step0() {
checkLastEntry(entries, 0, [8, 108, 8, 108, 8, 108, 8, 108, 0, vw, 0, vh, true]);
}
</script>

View File

@ -0,0 +1,98 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
.spacer {
height: calc(100vh + 100px);
}
</style>
<div id="leading-space" class="spacer"></div>
<div id="trailing-space" class="spacer"></div>
<script>
// Pick this number to be comfortably greater than the length of two frames at 60Hz.
var timeSkew = 40;
var topWindowEntries = [];
var iframeWindowEntries = [];
var targetIframe;
var topWindowTimeBeforeNotification;
var iframeWindowTimeBeforeNotification;
async_test(function(t) {
// assert_equals(window.innerWidth, 800, "Window must be 800 pixels wide.");
// assert_equals(window.innerHeight, 600, "Window must be 600 pixels high.");
t.step_timeout(function() {
targetIframe = document.createElement("iframe");
assert_true(!!targetIframe, "iframe exists");
targetIframe.src = "resources/timestamp-subframe.html";
var trailingSpace = document.getElementById("trailing-space");
assert_true(!!trailingSpace, "trailing-space exists");
trailingSpace.parentNode.insertBefore(targetIframe, trailingSpace);
targetIframe.onload = function() {
var target = targetIframe.contentDocument.getElementById("target");
var iframeScroller = targetIframe.contentDocument.scrollingElement;
// Observer created here, callback created in iframe context. Timestamps should be
// from this window.
var observer = new IntersectionObserver(
targetIframe.contentDocument.createObserverCallback(topWindowEntries), {});
assert_true(!!observer, "Observer exists");
observer.observe(target);
// Callback created here, observer created in iframe. Timestamps should be
// from iframe window.
observer = targetIframe.contentDocument.createObserver(function(newEntries) {
iframeWindowEntries = iframeWindowEntries.concat(newEntries);
});
observer.observe(target);
runTestCycle(step1, "First rAF after iframe is loaded.");
t.done();
};
}, timeSkew);
}, "Check that timestamps correspond to the to execution context that created the observer.");
function step1() {
document.scrollingElement.scrollTop = 200;
targetIframe.contentDocument.scrollingElement.scrollTop = 250;
topWindowTimeBeforeNotification = performance.now();
iframeWindowTimeBeforeNotification = targetIframe.contentWindow.performance.now();
runTestCycle(step2, "Generate notifications.");
assert_equals(topWindowEntries.length, 1, "One notification to top window observer.");
assert_equals(iframeWindowEntries.length, 1, "One notification to iframe observer.");
}
function step2() {
document.scrollingElement.scrollTop = 0;
var topWindowTimeAfterNotification = performance.now();
var iframeWindowTimeAfterNotification = targetIframe.contentWindow.performance.now();
// Test results are only significant if there's a gap between
// top window time and iframe window time.
assert_greater_than(topWindowTimeBeforeNotification, iframeWindowTimeAfterNotification,
"Time ranges for top and iframe windows are disjoint.");
assert_equals(topWindowEntries.length, 2, "Top window observer has two notifications.");
assert_between_inclusive(
topWindowEntries[1].time,
topWindowTimeBeforeNotification,
topWindowTimeAfterNotification,
"Notification to top window observer is within the expected range.");
assert_equals(iframeWindowEntries.length, 2, "Iframe observer has two notifications.");
assert_between_inclusive(
iframeWindowEntries[1].time,
iframeWindowTimeBeforeNotification,
iframeWindowTimeAfterNotification,
"Notification to iframe observer is within the expected range.");
}
</script>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
#root {
overflow: visible;
height: 200px;
width: 160px;
border: 7px solid black;
}
#target {
width: 100px;
height: 100px;
background-color: green;
}
</style>
<div id="root">
<div id="target" style="transform: translateY(300px)"></div>
</div>
<script>
var entries = [];
var target;
runTestCycle(function() {
target = document.getElementById("target");
assert_true(!!target, "target exists");
var root = document.getElementById("root");
assert_true(!!root, "root exists");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
}, {root: root});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
}, "Test that border bounding box is used to calculate intersection with a non-scrolling root.");
function step0() {
target.style.transform = "translateY(195px)";
runTestCycle(step1, "target.style.transform = 'translateY(195px)'");
checkLastEntry(entries, 0, [15, 115, 315, 415, 0, 0, 0, 0, 8, 182, 8, 222, false]);
}
function step1() {
target.style.transform = "";
checkLastEntry(entries, 1, [15, 115, 210, 310, 15, 115, 210, 222, 8, 182, 8, 222, true]);
}
</script>

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
#target {
width: 0px;
height: 0px;
position: fixed;
top: -1000px;
}
</style>
<div id='target'></div>
<script>
var vw = document.documentElement.clientWidth;
var vh = document.documentElement.clientHeight;
var entries = [];
runTestCycle(function() {
var target = document.getElementById('target');
assert_true(!!target, "target exists");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
}, "A zero-area hidden target should not be intersecting.");
function step0() {
checkLastEntry(entries, 0, [8, 8, -1000, -1000, 0, 0, 0, 0, 0, vw, 0, vh, false]);
}
</script>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/intersection-observer-test-utils.js"></script>
<style>
pre, #log {
position: absolute;
top: 0;
left: 200px;
}
#target {
width: 0px;
height: 0px;
}
</style>
<div id='target'></div>
<script>
var entries = [];
runTestCycle(function() {
var target = document.getElementById('target');
assert_true(!!target, "target exists");
var observer = new IntersectionObserver(function(changes) {
entries = entries.concat(changes)
});
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF should generate a notification.");
}, "Ensure that a zero-area target intersecting root generates a notification with intersectionRatio == 1");
function step0() {
assert_equals(entries.length, 1, "One notification.");
assert_equals(entries[0].intersectionRatio, 1, "intersectionRatio == 1");
}
</script>

View File

@ -9,16 +9,30 @@ this.topSites = class extends ExtensionAPI {
getAPI(context) {
return {
topSites: {
get: function() {
let urls = NewTabUtils.links.getLinks()
.filter(link => !!link)
.map(link => {
return {
url: link.url,
title: link.title,
};
});
return Promise.resolve(urls);
get: function(options) {
return new Promise(function(resolve) {
NewTabUtils.links.populateCache(function() {
let urls;
if (options && options.providers && options.providers.length > 0) {
let urlLists = options.providers.map(function(p) {
let provider = NewTabUtils[`${p}Provider`];
return provider ? NewTabUtils.getProviderLinks(provider).slice() : [];
});
urls = NewTabUtils.links.mergeLinkLists(urlLists);
} else {
urls = NewTabUtils.links.getLinks();
}
resolve(urls.filter(link => !!link)
.map(link => {
return {
url: link.url,
title: link.title,
};
}));
}, false);
});
},
},
};

View File

@ -46,6 +46,19 @@
"description": "Gets a list of top sites.",
"async": "callback",
"parameters": [
{
"type": "object",
"name": "options",
"properties": {
"providers": {
"type": "array",
"items": { "type": "string" },
"description": "Which providers to get top sites from. Possible values are \"places\" and \"activityStream\".",
"optional": true
}
},
"optional": true
},
{
"name": "callback",
"type": "function",

View File

@ -28,30 +28,29 @@ TestProvider.prototype = {
},
};
function makeLinks(links) {
add_task(async function test_topSites() {
// Important: To avoid test failures due to clock jitter on Windows XP, call
// Date.now() once here, not each time through the loop.
let frecency = 0;
let now = Date.now() * 1000;
let places = [];
links.map((link, i) => {
places.push({
url: link.url,
title: link.title,
lastVisitDate: now - i,
frecency: frecency++,
});
let provider1 = new TestProvider(done => {
let data = [{url: "http://example.com/", title: "site#-1", frecency: 9, lastVisitDate: now},
{url: "http://example0.com/", title: "site#0", frecency: 8, lastVisitDate: now},
{url: "http://example3.com/", title: "site#3", frecency: 5, lastVisitDate: now}];
done(data);
});
let provider2 = new TestProvider(done => {
let data = [{url: "http://example1.com/", title: "site#1", frecency: 7, lastVisitDate: now},
{url: "http://example2.com/", title: "site#2", frecency: 6, lastVisitDate: now}];
done(data);
});
return places;
}
add_task(async function test_topSites() {
let expect = [{url: "http://example.com/", title: "site#-1"},
{url: "http://example0.com/", title: "site#0"},
{url: "http://example1.com/", title: "site#1"},
{url: "http://example2.com/", title: "site#2"},
{url: "http://example3.com/", title: "site#3"}];
NewTabUtils.initWithoutProviders();
NewTabUtils.links.addProvider(provider1);
NewTabUtils.links.addProvider(provider2);
NewTabUtils.test1Provider = provider1;
NewTabUtils.test2Provider = provider2;
// Test that results from all providers are returned by default.
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": [
@ -59,27 +58,64 @@ add_task(async function test_topSites() {
],
},
background() {
// Tests consistent behaviour when no providers are specified.
browser.topSites.get(result => {
browser.test.sendMessage("done", result);
browser.test.sendMessage("done1", result);
});
browser.topSites.get({}, result => {
browser.test.sendMessage("done2", result);
});
browser.topSites.get({providers: []}, result => {
browser.test.sendMessage("done3", result);
});
// Tests that results are merged correctly.
browser.topSites.get({providers: ["test2", "test1"]}, result => {
browser.test.sendMessage("done4", result);
});
// Tests that only the specified provider is used.
browser.topSites.get({providers: ["test2"]}, result => {
browser.test.sendMessage("done5", result);
});
// Tests that specifying a non-existent provider returns an empty array.
browser.topSites.get({providers: ["fake"]}, result => {
browser.test.sendMessage("done6", result);
});
},
});
let expectedLinks = makeLinks(expect);
let provider = new TestProvider(done => done(expectedLinks));
NewTabUtils.initWithoutProviders();
NewTabUtils.links.addProvider(provider);
await NewTabUtils.links.populateCache();
await extension.startup();
let result = await extension.awaitMessage("done");
Assert.deepEqual(expect, result, "got topSites");
let expected1 = [{url: "http://example.com/", title: "site#-1"},
{url: "http://example0.com/", title: "site#0"},
{url: "http://example1.com/", title: "site#1"},
{url: "http://example2.com/", title: "site#2"},
{url: "http://example3.com/", title: "site#3"}];
let actual1 = await extension.awaitMessage("done1");
Assert.deepEqual(expected1, actual1, "got topSites");
let actual2 = await extension.awaitMessage("done2");
Assert.deepEqual(expected1, actual2, "got topSites");
let actual3 = await extension.awaitMessage("done3");
Assert.deepEqual(expected1, actual3, "got topSites");
let actual4 = await extension.awaitMessage("done4");
Assert.deepEqual(expected1, actual4, "got topSites");
let expected5 = [{url: "http://example1.com/", title: "site#1"},
{url: "http://example2.com/", title: "site#2"}];
let actual5 = await extension.awaitMessage("done5");
Assert.deepEqual(expected5, actual5, "got topSites");
let actual6 = await extension.awaitMessage("done6");
Assert.deepEqual([], actual6, "got topSites");
await extension.unload();
NewTabUtils.links.removeProvider(provider);
NewTabUtils.links.removeProvider(provider1);
NewTabUtils.links.removeProvider(provider2);
delete NewTabUtils.test1Provider;
delete NewTabUtils.test2Provider;
});

View File

@ -1453,6 +1453,14 @@ var Links = {
}
}
return this.mergeLinkLists(linkLists);
},
mergeLinkLists: function Links_mergeLinkLists(linkLists) {
if (linkLists.length == 1) {
return linkLists[0];
}
function getNextLink() {
let minLinks = null;
for (let links of linkLists) {