Bug 1323618 - Allow locking off of psuedo-classes through inIDOMUtils. r?heycam
MozReview-Commit-ID: DppYTmILpwy
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -1750,16 +1750,18 @@ var WalkerActor = protocol.ActorClassWit
*
* @param NodeActor node
* @param string pseudo
* A pseudoclass: ':hover', ':active', ':focus'
* @param options
* Options object:
* `parents`: True if the pseudo-class should be added
* 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
* be queued for any changed nodes.
*/
addPseudoClassLock: function (node, pseudo, options = {}) {
if (isNodeDead(node)) {
return;
}
@@ -1767,43 +1769,45 @@ var WalkerActor = protocol.ActorClassWit
// There can be only one node locked per pseudo, so dismiss all existing
// ones
for (let locked of this._activePseudoClassLocks) {
if (DOMUtils.hasPseudoClassLock(locked.rawNode, pseudo)) {
this._removePseudoClassLock(locked, pseudo);
}
}
- this._addPseudoClassLock(node, pseudo);
+ let enabled = options.enabled === undefined ||
+ options.enabled;
+ this._addPseudoClassLock(node, pseudo, enabled);
if (!options.parents) {
return;
}
let walker = this.getDocumentWalker(node.rawNode);
let cur;
while ((cur = walker.parentNode())) {
let curNode = this._ref(cur);
- this._addPseudoClassLock(curNode, pseudo);
+ this._addPseudoClassLock(curNode, pseudo, enabled);
}
},
_queuePseudoClassMutation: function (node) {
this.queueMutation({
target: node.actorID,
type: "pseudoClassLock",
pseudoClassLocks: node.writePseudoClassLocks()
});
},
- _addPseudoClassLock: function (node, pseudo) {
+ _addPseudoClassLock: function (node, pseudo, enabled) {
if (node.rawNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
return false;
}
- DOMUtils.addPseudoClassLock(node.rawNode, pseudo);
+ DOMUtils.addPseudoClassLock(node.rawNode, pseudo, enabled);
this._activePseudoClassLocks.add(node);
this._queuePseudoClassMutation(node);
return true;
},
_installHelperSheet: function (node) {
if (!this.installedHelpers) {
this.installedHelpers = new WeakMap();
--- a/devtools/server/tests/mochitest/inspector-traversal-data.html
+++ b/devtools/server/tests/mochitest/inspector-traversal-data.html
@@ -1,8 +1,9 @@
+<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Inspector Traversal Test Data</title>
<style type="text/css">
#pseudo::before {
content: "before";
}
--- a/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html
+++ b/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html
@@ -55,26 +55,31 @@ function checkChange(change, expectation
let target = change.target;
if (expectation.id)
is(target.id, expectation.id, "Expect a change on node id " + expectation.id);
if (expectation.nodeName)
is(target.nodeName, expectation.nodeName, "Expect a change on node name " + expectation.nodeName);
is(target.pseudoClassLocks.length, expectation.pseudos.length,
"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(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) {
if (!expectation.pseudos.some(expected => pseudo === expected)) {
ok(!target.hasPseudoClassLock(pseudo), "Don't expect lock: " + pseudo);
ok(!DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Don't expect lock in dom: " + pseudo);
-
}
}
}
function checkMutations(mutations, expectations) {
is(mutations.length, expectations.length, "Should get the right number of mutations.");
for (let i = 0; i < mutations.length; i++) {
checkChange(mutations[i] , expectations[i]);
@@ -88,17 +93,17 @@ addTest(function testPseudoClassLock() {
contentNode = gInspectee.querySelector("#b");
return promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(front => {
nodeFront = front;
// Lock the pseudoclass alone, no parents.
gWalker.addPseudoClassLock(nodeFront, ':active');
// Expect a single pseudoClassLock mutation.
return promiseOnce(gWalker, "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");
checkChange(mutations[0], {
id: "b",
nodeName: "DIV",
pseudos: [":active"]
});
}).then(() => {
// Now add :hover, this time with parents.
@@ -145,16 +150,28 @@ addTest(function testPseudoClassLock() {
pseudos: []
},
{
nodeName: 'HTML',
pseudos: []
}];
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(() => {
// Now shut down the walker and make sure that clears up the remaining lock.
return gWalker.release();
}).then(() => {
ok(!DOMUtils.hasPseudoClassLock(contentNode, ':active'), "Pseudoclass should have been removed during destruction.");
teardown();
}).then(runNextTest));
});
});
--- a/devtools/shared/specs/inspector.js
+++ b/devtools/shared/specs/inspector.js
@@ -229,17 +229,18 @@ const walkerSpec = generateActorSpec({
response: {
list: RetVal("array:array:string")
}
},
addPseudoClassLock: {
request: {
node: Arg(0, "domnode"),
pseudoClass: Arg(1),
- parents: Option(2)
+ parents: Option(2),
+ enabled: Option(2, "boolean"),
},
response: {}
},
hideNode: {
request: { node: Arg(0, "domnode") }
},
unhideNode: {
request: { node: Arg(0, "domnode") }
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -341,102 +341,110 @@ Element::Blur(mozilla::ErrorResult& aErr
if (win && fm) {
aError = fm->ClearFocus(win);
}
}
EventStates
Element::StyleStateFromLocks() const
{
- EventStates locks = LockedStyleStates();
- EventStates state = mState | locks;
-
- if (locks.HasState(NS_EVENT_STATE_VISITED)) {
+ StyleStateLocks locksAndValues = LockedStyleStates();
+ EventStates locks = locksAndValues.mLocks;
+ EventStates values = locksAndValues.mValues;
+ EventStates state = (mState & ~locks) | (locks & values);
+
+ if (state.HasState(NS_EVENT_STATE_VISITED)) {
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;
}
-EventStates
+Element::StyleStateLocks
Element::LockedStyleStates() const
{
- EventStates* locks =
- static_cast<EventStates*>(GetProperty(nsGkAtoms::lockedStyleStates));
+ StyleStateLocks* locks =
+ static_cast<StyleStateLocks*>(GetProperty(nsGkAtoms::lockedStyleStates));
if (locks) {
return *locks;
}
- return EventStates();
+ return StyleStateLocks();
}
void
Element::NotifyStyleStateChange(EventStates aStates)
{
nsIDocument* doc = GetComposedDoc();
if (doc) {
nsIPresShell *presShell = doc->GetShell();
if (presShell) {
nsAutoScriptBlocker scriptBlocker;
presShell->ContentStateChanged(doc, this, aStates);
}
}
}
void
-Element::LockStyleStates(EventStates aStates)
+Element::LockStyleStates(EventStates aStates, bool aEnabled)
{
- EventStates* locks = new EventStates(LockedStyleStates());
-
- *locks |= aStates;
+ StyleStateLocks* locks = new StyleStateLocks(LockedStyleStates());
+
+ locks->mLocks |= aStates;
+ if (aEnabled) {
+ locks->mValues |= aStates;
+ } else {
+ locks->mValues &= ~aStates;
+ }
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)) {
- *locks &= ~NS_EVENT_STATE_VISITED;
+ locks->mLocks &= ~NS_EVENT_STATE_VISITED;
}
SetProperty(nsGkAtoms::lockedStyleStates, locks,
- nsINode::DeleteProperty<EventStates>);
+ nsINode::DeleteProperty<StyleStateLocks>);
SetHasLockedStyleStates();
NotifyStyleStateChange(aStates);
}
void
Element::UnlockStyleStates(EventStates aStates)
{
- EventStates* locks = new EventStates(LockedStyleStates());
-
- *locks &= ~aStates;
-
- if (locks->IsEmpty()) {
+ StyleStateLocks* locks = new StyleStateLocks(LockedStyleStates());
+
+ locks->mLocks &= ~aStates;
+
+ if (locks->mLocks.IsEmpty()) {
DeleteProperty(nsGkAtoms::lockedStyleStates);
ClearHasLockedStyleStates();
delete locks;
}
else {
SetProperty(nsGkAtoms::lockedStyleStates, locks,
- nsINode::DeleteProperty<EventStates>);
+ nsINode::DeleteProperty<StyleStateLocks>);
}
NotifyStyleStateChange(aStates);
}
void
Element::ClearStyleStateLocks()
{
- EventStates locks = LockedStyleStates();
+ StyleStateLocks locks = LockedStyleStates();
DeleteProperty(nsGkAtoms::lockedStyleStates);
ClearHasLockedStyleStates();
- NotifyStyleStateChange(locks);
+ NotifyStyleStateChange(locks.mLocks);
}
bool
Element::GetBindingURL(nsIDocument *aDocument, css::URLValue **aResult)
{
// If we have a frame the frame has already loaded the binding. And
// otherwise, don't do anything else here unless we're dealing with
// XUL or an HTML element that may have a plugin-related overlay
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -240,24 +240,36 @@ public:
{
if (!HasLockedStyleStates()) {
return mState;
}
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.
*/
- EventStates LockedStyleStates() const;
+ StyleStateLocks LockedStyleStates() const;
/**
* 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.
*/
void UnlockStyleStates(EventStates aStates);
/**
* Clear all style state locks on this element.
--- a/layout/inspector/inDOMUtils.cpp
+++ b/layout/inspector/inDOMUtils.cpp
@@ -1249,27 +1249,29 @@ inDOMUtils::GetCSSPseudoElementNames(uin
ret[i] = ToNewUnicode(nsDependentAtomString(array[i]));
}
*aNames = ret;
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::AddPseudoClassLock(nsIDOMElement *aElement,
- const nsAString &aPseudoClass)
+ const nsAString &aPseudoClass,
+ bool aEnabled,
+ uint8_t aArgc)
{
EventStates state = GetStatesForPseudoClass(aPseudoClass);
if (state.IsEmpty()) {
return NS_OK;
}
nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement);
NS_ENSURE_ARG_POINTER(element);
- element->LockStyleStates(state);
+ element->LockStyleStates(state, aArgc > 0 ? aEnabled : true);
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::RemovePseudoClassLock(nsIDOMElement *aElement,
const nsAString &aPseudoClass)
{
@@ -1295,17 +1297,17 @@ inDOMUtils::HasPseudoClassLock(nsIDOMEle
if (state.IsEmpty()) {
*_retval = false;
return NS_OK;
}
nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aElement);
NS_ENSURE_ARG_POINTER(element);
- EventStates locks = element->LockedStyleStates();
+ EventStates locks = element->LockedStyleStates().mLocks;
*_retval = locks.HasAllStates(state);
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::ClearPseudoClassLocks(nsIDOMElement *aElement)
{
--- a/layout/inspector/inIDOMUtils.idl
+++ b/layout/inspector/inIDOMUtils.idl
@@ -180,18 +180,21 @@ interface inIDOMUtils : nsISupports
* @param {unsigned long} aCount the number of items returned
* @param {wstring[]} aNames the names
*/
void getCSSPseudoElementNames([optional] out unsigned long aCount,
[retval, array, size_is(aCount)] out wstring aNames);
// pseudo-class style locking methods. aPseudoClass must be a valid pseudo-class
// selector string, e.g. ":hover". ":any-link" and non-event-state
- // pseudo-classes are ignored.
- void addPseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass);
+ // pseudo-classes are ignored. aEnabled sets whether the psuedo-class
+ // 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);
bool hasPseudoClassLock(in nsIDOMElement aElement, in DOMString aPseudoClass);
void clearPseudoClassLocks(in nsIDOMElement aElement);
/**
* Parse CSS and update the style sheet in place.
*
* @param DOMCSSStyleSheet aSheet