Bug 1323618 - Allow locking off of psuedo-classes through inIDOMUtils. r=heycam

MozReview-Commit-ID: DppYTmILpwy

--HG--
extra : rebase_source : c0befc8f86b92cfc88b537e520c3aa032846ba6a
This commit is contained in:
Jared Wein 2017-01-12 12:10:07 -05:00
parent 90c9daf114
commit 072c1a6ba3
8 changed files with 84 additions and 36 deletions

View File

@ -1755,6 +1755,8 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
* Options object: * Options object:
* `parents`: True if the pseudo-class should be added * `parents`: True if the pseudo-class should be added
* to parent nodes. * to parent nodes.
* `enabled`: False if the pseudo-class should be locked
* to 'off'. Defaults to true.
* *
* @returns An empty packet. A "pseudoClassLock" mutation will * @returns An empty packet. A "pseudoClassLock" mutation will
* be queued for any changed nodes. * be queued for any changed nodes.
@ -1772,7 +1774,9 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
} }
} }
this._addPseudoClassLock(node, pseudo); let enabled = options.enabled === undefined ||
options.enabled;
this._addPseudoClassLock(node, pseudo, enabled);
if (!options.parents) { if (!options.parents) {
return; return;
@ -1782,7 +1786,7 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
let cur; let cur;
while ((cur = walker.parentNode())) { while ((cur = walker.parentNode())) {
let curNode = this._ref(cur); let curNode = this._ref(cur);
this._addPseudoClassLock(curNode, pseudo); this._addPseudoClassLock(curNode, pseudo, enabled);
} }
}, },
@ -1794,11 +1798,11 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
}); });
}, },
_addPseudoClassLock: function (node, pseudo) { _addPseudoClassLock: function (node, pseudo, enabled) {
if (node.rawNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) { if (node.rawNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
return false; return false;
} }
DOMUtils.addPseudoClassLock(node.rawNode, pseudo); DOMUtils.addPseudoClassLock(node.rawNode, pseudo, enabled);
this._activePseudoClassLocks.add(node); this._activePseudoClassLocks.add(node);
this._queuePseudoClassMutation(node); this._queuePseudoClassMutation(node);
return true; return true;

View File

@ -1,3 +1,4 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">

View File

@ -60,16 +60,21 @@ function checkChange(change, expectation) {
is(target.pseudoClassLocks.length, expectation.pseudos.length, is(target.pseudoClassLocks.length, expectation.pseudos.length,
"Expect " + expectation.pseudos.length + " pseudoclass locks."); "Expect " + expectation.pseudos.length + " pseudoclass locks.");
for (let pseudo of expectation.pseudos) { for (let i = 0; i < expectation.pseudos.length; i++) {
let pseudo = expectation.pseudos[i];
let enabled = expectation.enabled === undefined ? true : expectation.enabled[i];
ok(target.hasPseudoClassLock(pseudo), "Expect lock: " + pseudo); ok(target.hasPseudoClassLock(pseudo), "Expect lock: " + pseudo);
ok(DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Expect lock in dom: " + pseudo); let rawNode = target.rawNode();
ok(DOMUtils.hasPseudoClassLock(rawNode, pseudo), "Expect lock in dom: " + pseudo);
is(rawNode.matches(pseudo), enabled,
`Target should match pseudoclass, '${pseudo}', if enabled (with .matches())`);
} }
for (let pseudo of KNOWN_PSEUDOCLASSES) { for (let pseudo of KNOWN_PSEUDOCLASSES) {
if (!expectation.pseudos.some(expected => pseudo === expected)) { if (!expectation.pseudos.some(expected => pseudo === expected)) {
ok(!target.hasPseudoClassLock(pseudo), "Don't expect lock: " + pseudo); ok(!target.hasPseudoClassLock(pseudo), "Don't expect lock: " + pseudo);
ok(!DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Don't expect lock in dom: " + pseudo); ok(!DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Don't expect lock in dom: " + pseudo);
} }
} }
} }
@ -93,7 +98,7 @@ addTest(function testPseudoClassLock() {
// Expect a single pseudoClassLock mutation. // Expect a single pseudoClassLock mutation.
return promiseOnce(gWalker, "mutations"); return promiseOnce(gWalker, "mutations");
}).then(mutations => { }).then(mutations => {
is(mutations.length, 1, "Should get one mutations"); is(mutations.length, 1, "Should get one mutation");
is(mutations[0].target, nodeFront, "Should be the node we tried to apply to"); is(mutations[0].target, nodeFront, "Should be the node we tried to apply to");
checkChange(mutations[0], { checkChange(mutations[0], {
id: "b", id: "b",
@ -149,6 +154,18 @@ addTest(function testPseudoClassLock() {
pseudos: [] pseudos: []
}]; }];
checkMutations(mutations, expectedMutations); checkMutations(mutations, expectedMutations);
}).then(() => {
gWalker.addPseudoClassLock(nodeFront, ":hover", {enabled: false});
return promiseOnce(gWalker, "mutations");
}).then(mutations => {
is(mutations.length, 1, "Should get one mutation");
is(mutations[0].target, nodeFront, "Should be the node we tried to apply to");
checkChange(mutations[0], {
id: "b",
nodeName: "DIV",
pseudos: [":hover", ":active"],
enabled: [false, true]
});
}).then(() => { }).then(() => {
// Now shut down the walker and make sure that clears up the remaining lock. // Now shut down the walker and make sure that clears up the remaining lock.
return gWalker.release(); return gWalker.release();

View File

@ -234,7 +234,8 @@ const walkerSpec = generateActorSpec({
request: { request: {
node: Arg(0, "domnode"), node: Arg(0, "domnode"),
pseudoClass: Arg(1), pseudoClass: Arg(1),
parents: Option(2) parents: Option(2),
enabled: Option(2, "boolean"),
}, },
response: {} response: {}
}, },

View File

@ -346,27 +346,30 @@ Element::Blur(mozilla::ErrorResult& aError)
EventStates EventStates
Element::StyleStateFromLocks() const Element::StyleStateFromLocks() const
{ {
EventStates locks = LockedStyleStates(); StyleStateLocks locksAndValues = LockedStyleStates();
EventStates state = mState | locks; EventStates locks = locksAndValues.mLocks;
EventStates values = locksAndValues.mValues;
EventStates state = (mState & ~locks) | (locks & values);
if (locks.HasState(NS_EVENT_STATE_VISITED)) { if (state.HasState(NS_EVENT_STATE_VISITED)) {
return state & ~NS_EVENT_STATE_UNVISITED; return state & ~NS_EVENT_STATE_UNVISITED;
} }
if (locks.HasState(NS_EVENT_STATE_UNVISITED)) { if (state.HasState(NS_EVENT_STATE_UNVISITED)) {
return state & ~NS_EVENT_STATE_VISITED; return state & ~NS_EVENT_STATE_VISITED;
} }
return state; return state;
} }
EventStates Element::StyleStateLocks
Element::LockedStyleStates() const Element::LockedStyleStates() const
{ {
EventStates* locks = StyleStateLocks* locks =
static_cast<EventStates*>(GetProperty(nsGkAtoms::lockedStyleStates)); static_cast<StyleStateLocks*>(GetProperty(nsGkAtoms::lockedStyleStates));
if (locks) { if (locks) {
return *locks; return *locks;
} }
return EventStates(); return StyleStateLocks();
} }
void void
@ -383,21 +386,26 @@ Element::NotifyStyleStateChange(EventStates aStates)
} }
void void
Element::LockStyleStates(EventStates aStates) Element::LockStyleStates(EventStates aStates, bool aEnabled)
{ {
EventStates* locks = new EventStates(LockedStyleStates()); StyleStateLocks* locks = new StyleStateLocks(LockedStyleStates());
*locks |= aStates; locks->mLocks |= aStates;
if (aEnabled) {
locks->mValues |= aStates;
} else {
locks->mValues &= ~aStates;
}
if (aStates.HasState(NS_EVENT_STATE_VISITED)) { if (aStates.HasState(NS_EVENT_STATE_VISITED)) {
*locks &= ~NS_EVENT_STATE_UNVISITED; locks->mLocks &= ~NS_EVENT_STATE_UNVISITED;
} }
if (aStates.HasState(NS_EVENT_STATE_UNVISITED)) { if (aStates.HasState(NS_EVENT_STATE_UNVISITED)) {
*locks &= ~NS_EVENT_STATE_VISITED; locks->mLocks &= ~NS_EVENT_STATE_VISITED;
} }
SetProperty(nsGkAtoms::lockedStyleStates, locks, SetProperty(nsGkAtoms::lockedStyleStates, locks,
nsINode::DeleteProperty<EventStates>); nsINode::DeleteProperty<StyleStateLocks>);
SetHasLockedStyleStates(); SetHasLockedStyleStates();
NotifyStyleStateChange(aStates); NotifyStyleStateChange(aStates);
@ -406,18 +414,18 @@ Element::LockStyleStates(EventStates aStates)
void void
Element::UnlockStyleStates(EventStates aStates) Element::UnlockStyleStates(EventStates aStates)
{ {
EventStates* locks = new EventStates(LockedStyleStates()); StyleStateLocks* locks = new StyleStateLocks(LockedStyleStates());
*locks &= ~aStates; locks->mLocks &= ~aStates;
if (locks->IsEmpty()) { if (locks->mLocks.IsEmpty()) {
DeleteProperty(nsGkAtoms::lockedStyleStates); DeleteProperty(nsGkAtoms::lockedStyleStates);
ClearHasLockedStyleStates(); ClearHasLockedStyleStates();
delete locks; delete locks;
} }
else { else {
SetProperty(nsGkAtoms::lockedStyleStates, locks, SetProperty(nsGkAtoms::lockedStyleStates, locks,
nsINode::DeleteProperty<EventStates>); nsINode::DeleteProperty<StyleStateLocks>);
} }
NotifyStyleStateChange(aStates); NotifyStyleStateChange(aStates);
@ -426,12 +434,12 @@ Element::UnlockStyleStates(EventStates aStates)
void void
Element::ClearStyleStateLocks() Element::ClearStyleStateLocks()
{ {
EventStates locks = LockedStyleStates(); StyleStateLocks locks = LockedStyleStates();
DeleteProperty(nsGkAtoms::lockedStyleStates); DeleteProperty(nsGkAtoms::lockedStyleStates);
ClearHasLockedStyleStates(); ClearHasLockedStyleStates();
NotifyStyleStateChange(locks); NotifyStyleStateChange(locks.mLocks);
} }
bool bool

View File

@ -244,15 +244,27 @@ public:
return StyleStateFromLocks(); return StyleStateFromLocks();
} }
/**
* StyleStateLocks is used to specify which event states should be locked,
* and whether they should be locked to on or off.
*/
struct StyleStateLocks {
// mLocks tracks which event states should be locked.
EventStates mLocks;
// mValues tracks if the locked state should be on or off.
EventStates mValues;
};
/** /**
* The style state locks applied to this element. * The style state locks applied to this element.
*/ */
EventStates LockedStyleStates() const; StyleStateLocks LockedStyleStates() const;
/** /**
* Add a style state lock on this element. * Add a style state lock on this element.
* aEnabled is the value to lock the given state bits to.
*/ */
void LockStyleStates(EventStates aStates); void LockStyleStates(EventStates aStates, bool aEnabled);
/** /**
* Remove a style state lock on this element. * Remove a style state lock on this element.

View File

@ -1254,7 +1254,9 @@ inDOMUtils::GetCSSPseudoElementNames(uint32_t* aLength, char16_t*** aNames)
NS_IMETHODIMP NS_IMETHODIMP
inDOMUtils::AddPseudoClassLock(nsIDOMElement *aElement, inDOMUtils::AddPseudoClassLock(nsIDOMElement *aElement,
const nsAString &aPseudoClass) const nsAString &aPseudoClass,
bool aEnabled,
uint8_t aArgc)
{ {
EventStates state = GetStatesForPseudoClass(aPseudoClass); EventStates state = GetStatesForPseudoClass(aPseudoClass);
if (state.IsEmpty()) { if (state.IsEmpty()) {
@ -1264,7 +1266,7 @@ inDOMUtils::AddPseudoClassLock(nsIDOMElement *aElement,
nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement); nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement);
NS_ENSURE_ARG_POINTER(element); NS_ENSURE_ARG_POINTER(element);
element->LockStyleStates(state); element->LockStyleStates(state, aArgc > 0 ? aEnabled : true);
return NS_OK; return NS_OK;
} }
@ -1300,7 +1302,7 @@ inDOMUtils::HasPseudoClassLock(nsIDOMElement *aElement,
nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement); nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement);
NS_ENSURE_ARG_POINTER(element); NS_ENSURE_ARG_POINTER(element);
EventStates locks = element->LockedStyleStates(); EventStates locks = element->LockedStyleStates().mLocks;
*_retval = locks.HasAllStates(state); *_retval = locks.HasAllStates(state);
return NS_OK; return NS_OK;

View File

@ -185,8 +185,11 @@ interface inIDOMUtils : nsISupports
// pseudo-class style locking methods. aPseudoClass must be a valid pseudo-class // pseudo-class style locking methods. aPseudoClass must be a valid pseudo-class
// selector string, e.g. ":hover". ":any-link" and non-event-state // selector string, e.g. ":hover". ":any-link" and non-event-state
// pseudo-classes are ignored. // pseudo-classes are ignored. aEnabled sets whether the psuedo-class
void addPseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass); // should be locked to on or off.
[optional_argc] void addPseudoClassLock(in nsIDOMElement aElement,
in DOMString aPseudoClass,
[optional] in boolean aEnabled);
void removePseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass); void removePseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass);
bool hasPseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass); bool hasPseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass);
void clearPseudoClassLocks(in nsIDOMElement aElement); void clearPseudoClassLocks(in nsIDOMElement aElement);