Bug 886037 - Add a styles actor for the style inspectors. r=jwalker

--HG--
extra : rebase_source : 80aa9dc491c77f4b73036daeddd48f51c40f015a
This commit is contained in:
Dave Camp 2013-07-23 15:51:58 -07:00
Родитель 48d351efc5
Коммит 9383975f56
11 изменённых файлов: 1367 добавлений и 6 удалений

Просмотреть файл

@ -60,6 +60,7 @@ const object = require("sdk/util/object");
const events = require("sdk/event/core");
const { Unknown } = require("sdk/platform/xpcom");
const { Class } = require("sdk/core/heritage");
const {PageStyleActor} = require("devtools/server/actors/styles");
const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
@ -2080,7 +2081,8 @@ var InspectorActor = protocol.ActorClass({
var domReady = () => {
let tabActor = this.tabActor;
window.removeEventListener("DOMContentLoaded", domReady, true);
deferred.resolve(WalkerActor(this.conn, window.document, tabActor._tabbrowser, options));
this.walker = WalkerActor(this.conn, window.document, tabActor._tabbrowser, options);
deferred.resolve(this.walker);
};
if (window.document.readyState === "loading") {
@ -2095,6 +2097,20 @@ var InspectorActor = protocol.ActorClass({
response: {
walker: RetVal("domwalker")
}
}),
getPageStyle: method(function() {
if (this._pageStylePromise) {
return this._pageStylePromise;
}
this._pageStylePromise = this.getWalker().then(walker => {
return PageStyleActor(this);
});
return this._pageStylePromise;
}, {
request: {},
response: { pageStyle: RetVal("pagestyle") }
})
});
@ -2111,7 +2127,31 @@ var InspectorFront = exports.InspectorFront = protocol.FrontClass(InspectorActor
// library, so we're going to self-own on the client side for now.
client.addActorPool(this);
this.manage(this);
}
},
getWalker: protocol.custom(function() {
return this._getWalker().then(walker => {
this.walker = walker;
return walker;
});
}, {
impl: "_getWalker"
}),
getPageStyle: protocol.custom(function() {
return this._getPageStyle().then(pageStyle => {
// We need a walker to understand node references from the
// node style.
if (this.walker) {
return pageStyle;
}
return this.getWalker().then(() => {
return pageStyle;
});
});
}, {
impl: "_getPageStyle"
})
});
function documentWalker(node, whatToShow=Ci.nsIDOMNodeFilter.SHOW_ALL) {

Просмотреть файл

@ -0,0 +1,757 @@
/* 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/. */
"use strict";
const {Cc, Ci} = require("chrome");
const protocol = require("devtools/server/protocol");
const {Arg, Option, method, RetVal, types} = protocol;
const events = require("sdk/event/core");
const object = require("sdk/util/object");
const { Class } = require("sdk/core/heritage");
loader.lazyGetter(this, "CssLogic", () => require("devtools/styleinspector/css-logic").CssLogic);
loader.lazyGetter(this, "DOMUtils", () => Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils));
// The PageStyle actor flattens the DOM CSS objects a little bit, merging
// Rules and their Styles into one actor. For elements (which have a style
// but no associated rule) we fake a rule with the following style id.
const ELEMENT_STYLE = 100;
exports.ELEMENT_STYLE = ELEMENT_STYLE;
// Predeclare the domnode actor type for use in requests.
types.addActorType("domnode");
/**
* DOM Nodes returned by the style actor will be owned by the DOM walker
* for the connection.
*/
types.addLifetime("walker", "walker");
/**
* When asking for the styles applied to a node, we return a list of
* appliedstyle json objects that lists the rules that apply to the node
* and which element they were inherited from (if any).
*/
types.addDictType("appliedstyle", {
rule: "domstylerule#actorid",
inherited: "nullable:domnode#actorid"
});
types.addDictType("matchedselector", {
rule: "domstylerule#actorid",
selector: "string",
value: "string",
status: "number"
});
/**
* The PageStyle actor lets the client look at the styles on a page, as
* they are applied to a given node.
*/
var PageStyleActor = protocol.ActorClass({
typeName: "pagestyle",
/**
* Create a PageStyleActor.
*
* @param inspector
* The InspectorActor that owns this PageStyleActor.
*
* @constructor
*/
initialize: function(inspector) {
protocol.Actor.prototype.initialize.call(this, null);
this.inspector = inspector;
if (!this.inspector.walker) {
throw Error("The inspector's WalkerActor must be created before " +
"creating a PageStyleActor.");
}
this.walker = inspector.walker;
this.cssLogic = new CssLogic;
// Stores the association of DOM objects -> actors
this.refMap = new Map;
},
get conn() this.inspector.conn,
/**
* Return or create a StyleRuleActor for the given item.
* @param item Either a CSSStyleRule or a DOM element.
*/
_styleRef: function(item) {
if (this.refMap.has(item)) {
return this.refMap.get(item);
}
let actor = StyleRuleActor(this, item);
this.manage(actor);
this.refMap.set(item, actor);
return actor;
},
/**
* Return or create a StyleSheetActor for the given
* nsIDOMCSSStyleSheet
*/
_sheetRef: function(sheet) {
if (this.refMap.has(sheet)) {
return this.refMap.get(sheet);
}
let actor = StyleSheetActor(this, sheet);
this.manage(actor);
this.refMap.set(sheet, actor);
return actor;
},
/**
* Get the computed style for a node.
*
* @param NodeActor node
* @param object options
* `filter`: A string filter that affects the "matched" handling.
* 'user': Include properties from user style sheets.
* 'ua': Include properties from user and user-agent sheets.
* Default value is 'ua'
* `markMatched`: true if you want the 'matched' property to be added
* when a computed property has been modified by a style included
* by `filter`.
* `onlyMatched`: true if unmatched properties shouldn't be included.
*
* @returns a JSON blob with the following form:
* {
* "property-name": {
* value: "property-value",
* priority: "!important" <optional>
* matched: <true if there are matched selectors for this value>
* },
* ...
* }
*/
getComputed: method(function(node, options) {
let win = node.rawNode.ownerDocument.defaultView;
let ret = Object.create(null);
this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
this.cssLogic.highlight(node.rawNode);
let computed = this.cssLogic._computedStyle;
Array.prototype.forEach.call(computed, name => {
let matched = undefined;
ret[name] = {
value: computed.getPropertyValue(name),
priority: computed.getPropertyPriority(name) || undefined
};
});
if (options.markMatched || options.onlyMatched) {
let matched = this.cssLogic.hasMatchedSelectors(Object.keys(ret));
for (let key in ret) {
if (matched[key]) {
ret[key].matched = options.markMatched ? true : undefined
} else if (options.onlyMatched) {
delete ret[key];
}
}
}
return ret;
}, {
request: {
node: Arg(0, "domnode"),
markMatched: Option(1, "boolean"),
onlyMatched: Option(1, "boolean"),
filter: Option(1, "string"),
},
response: {
computed: RetVal("json")
}
}),
/**
* Get a list of selectors that match a given property for a node.
*
* @param NodeActor node
* @param string property
* @param object options
* `filter`: A string filter that affects the "matched" handling.
* 'user': Include properties from user style sheets.
* 'ua': Include properties from user and user-agent sheets.
* Default value is 'ua'
*
* @returns a JSON object with the following form:
* {
* // An ordered list of rules that apply
* matched: [{
* rule: <rule actorid>,
* sourceText: <string>, // The source of the selector, relative
* // to the node in question.
* selector: <string>, // the selector ID that matched
* value: <string>, // the value of the property
* status: <int>,
* // The status of the match - high numbers are better placed
* // to provide styling information:
* // 3: Best match, was used.
* // 2: Matched, but was overridden.
* // 1: Rule from a parent matched.
* // 0: Unmatched (never returned in this API)
* }, ...],
*
* // The full form of any domrule referenced.
* rules: [ <domrule>, ... ], // The full form of any domrule referenced
*
* // The full form of any sheets referenced.
* sheets: [ <domsheet>, ... ]
* }
*/
getMatchedSelectors: method(function(node, property, options) {
this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
this.cssLogic.highlight(node.rawNode);
let walker = node.parent();
let rules = new Set;
let sheets = new Set;
let matched = [];
let propInfo = this.cssLogic.getPropertyInfo(property);
for (let selectorInfo of propInfo.matchedSelectors) {
let cssRule = selectorInfo.selector._cssRule;
let domRule = cssRule.sourceElement || cssRule._domRule;
let rule = this._styleRef(domRule);
rules.add(rule);
matched.push({
rule: rule,
sourceText: this.getSelectorSource(selectorInfo, node.rawNode),
selector: selectorInfo.selector.text,
value: selectorInfo.value,
status: selectorInfo.status
});
}
this.expandSets(rules, sheets);
return {
matched: matched,
rules: [...rules],
sheets: [...sheets],
}
}, {
request: {
node: Arg(0, "domnode"),
property: Arg(1, "string"),
filter: Option(2, "string")
},
response: RetVal(types.addDictType("matchedselectorresponse", {
rules: "array:domstylerule",
sheets: "array:domsheet",
matched: "array:matchedselector"
}))
}),
// Get a selector source for a CssSelectorInfo relative to a given
// node.
getSelectorSource: function(selectorInfo, relativeTo) {
let result = selectorInfo.selector.text;
if (selectorInfo.elementStyle) {
let source = selectorInfo.sourceElement;
if (source === relativeTo) {
result = "this";
} else {
result = CssLogic.getShortName(source);
}
result += ".style"
}
return result;
},
/**
* Get the set of styles that apply to a given node.
* @param NodeActor node
* @param string property
* @param object options
* `filter`: A string filter that affects the "matched" handling.
* 'user': Include properties from user style sheets.
* 'ua': Include properties from user and user-agent sheets.
* Default value is 'ua'
* `inherited`: Include styles inherited from parent nodes.
* `matchedSeletors`: Include an array of specific selectors that
* caused this rule to match its node.
*/
getApplied: method(function(node, options) {
let entries = [];
this.addElementRules(node.rawNode, undefined, options, entries);
if (options.inherited) {
let parent = this.walker.parentNode(node);
while (parent && parent.rawNode.nodeType != Ci.nsIDOMNode.DOCUMENT_NODE) {
this.addElementRules(parent.rawNode, parent, options, entries);
parent = this.walker.parentNode(parent);
}
}
if (options.matchedSelectors) {
for (let entry of entries) {
if (entry.rule.type === ELEMENT_STYLE) {
continue;
}
let domRule = entry.rule.rawRule;
let selectors = CssLogic.getSelectors(domRule);
let element = entry.inherited ? entry.inherited.rawNode : node.rawNode;
entry.matchedSelectors = [];
for (let i = 0; i < selectors.length; i++) {
if (DOMUtils.selectorMatchesElement(element, domRule, i)) {
entry.matchedSelectors.push(selectors[i]);
}
}
}
}
let rules = new Set;
let sheets = new Set;
entries.forEach(entry => rules.add(entry.rule));
this.expandSets(rules, sheets);
return {
entries: entries,
rules: [...rules],
sheets: [...sheets]
}
}, {
request: {
node: Arg(0, "domnode"),
inherited: Option(1, "boolean"),
matchedSelectors: Option(1, "boolean"),
filter: Option(1, "string")
},
response: RetVal(types.addDictType("appliedStylesReturn", {
entries: "array:appliedstyle",
rules: "array:domstylerule",
sheets: "array:domsheet"
}))
}),
_hasInheritedProps: function(style) {
return Array.prototype.some.call(style, prop => {
return DOMUtils.isInheritedProperty(prop);
});
},
/**
* Helper function for getApplied, adds all the rules from a given
* element.
*/
addElementRules: function(element, inherited, options, rules)
{
let elementStyle = this._styleRef(element);
if (!inherited || this._hasInheritedProps(element.style)) {
rules.push({
rule: elementStyle,
inherited: inherited,
});
}
// Get the styles that apply to the element.
let domRules = DOMUtils.getCSSStyleRules(element);
// getCSSStyleRules returns ordered from least-specific to
// most-specific.
for (let i = domRules.Count() - 1; i >= 0; i--) {
let domRule = domRules.GetElementAt(i);
let isSystem = !CssLogic.isContentStylesheet(domRule.parentStyleSheet);
if (isSystem && options.filter != CssLogic.FILTER.UA) {
continue;
}
if (inherited) {
// Don't include inherited rules if none of its properties
// are inheritable.
let hasInherited = Array.prototype.some.call(domRule.style, prop => {
return DOMUtils.isInheritedProperty(prop);
});
if (!hasInherited) {
continue;
}
}
let ruleActor = this._styleRef(domRule);
rules.push({
rule: ruleActor,
inherited: inherited,
});
}
},
/**
* Expand Sets of rules and sheets to include all parent rules and sheets.
*/
expandSets: function(ruleSet, sheetSet) {
// Sets include new items in their iteration
for (let rule of ruleSet) {
if (rule.rawRule.parentRule) {
let parent = this._styleRef(rule.rawRule.parentRule);
if (!ruleSet.has(parent)) {
ruleSet.add(parent);
}
}
if (rule.rawRule.parentStyleSheet) {
let parent = this._sheetRef(rule.rawRule.parentStyleSheet);
if (!sheetSet.has(parent)) {
sheetSet.add(parent);
}
}
}
for (let sheet of sheetSet) {
if (sheet.rawSheet.parentStyleSheet) {
let parent = this._sheetRef(sheet.rawSheet.parentStyleSheet);
if (!sheetSet.has(parent)) {
sheetSet.add(parent);
}
}
}
}
});
exports.PageStyleActor = PageStyleActor;
/**
* Front object for the PageStyleActor
*/
var PageStyleFront = protocol.FrontClass(PageStyleActor, {
initialize: function(conn, form, ctx, detail) {
protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
this.inspector = this.parent();
},
destroy: function() {
protocol.Front.prototype.destroy.call(this);
},
get walker() {
return this.inspector.walker;
},
getMatchedSelectors: protocol.custom(function(node, property, options) {
return this._getMatchedSelectors(node, property, options).then(ret => {
return ret.matched;
});
}, {
impl: "_getMatchedSelectors"
}),
getApplied: protocol.custom(function(node, options={}) {
return this._getApplied(node, options).then(ret => {
return ret.entries;
});
}, {
impl: "_getApplied"
})
});
/**
* Actor representing an nsIDOMCSSStyleSheet.
*/
var StyleSheetActor = protocol.ActorClass({
typeName: "domsheet",
initialize: function(pageStyle, sheet) {
protocol.Front.prototype.initialize.call(this);
this.pageStyle = pageStyle;
this.rawSheet = sheet;
},
get conn() this.pageStyle.conn,
form: function(detail) {
if (detail === "actorid") {
return this.actorID;
}
return {
actor: this.actorID,
// href stores the uri of the sheet
href: this.rawSheet.href,
// nodeHref stores the URI of the document that
// included the sheet.
nodeHref: this.rawSheet.ownerNode ? this.rawSheet.ownerNode.ownerDocument.location.href : undefined,
system: !CssLogic.isContentStylesheet(this.rawSheet),
disabled: this.rawSheet.disabled ? true : undefined
}
}
});
/**
* Front for the StyleSheetActor.
*/
var StyleSheetFront = protocol.FrontClass(StyleSheetActor, {
initialize: function(conn, form, ctx, detail) {
protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
},
form: function(form, detail) {
if (detail === "actorid") {
this.actorID = form;
return;
}
this.actorID = form.actorID;
this._form = form;
},
get href() this._form.href,
get nodeHref() this._form.nodeHref,
get disabled() !!this._form.disabled,
get isSystem() this._form.system
});
// Predeclare the domstylerule actor type
types.addActorType("domstylerule");
/**
* An actor that represents a CSS style object on the protocol.
*
* We slightly flatten the CSSOM for this actor, it represents
* both the CSSRule and CSSStyle objects in one actor. For nodes
* (which have a CSSStyle but no CSSRule) we create a StyleRuleActor
* with a special rule type (100).
*/
var StyleRuleActor = protocol.ActorClass({
typeName: "domstylerule",
initialize: function(pageStyle, item) {
protocol.Actor.prototype.initialize.call(this, null);
this.pageStyle = pageStyle;
this.rawStyle = item.style;
if (item instanceof (Ci.nsIDOMCSSRule)) {
this.type = item.type;
this.rawRule = item;
if (this.rawRule instanceof Ci.nsIDOMCSSStyleRule && this.rawRule.parentStyleSheet) {
this.line = DOMUtils.getRuleLine(this.rawRule);
}
} else {
// Fake a rule
this.type = ELEMENT_STYLE;
this.rawNode = item;
this.rawRule = {
style: item.style,
toString: function() "[element rule " + this.style + "]"
}
}
},
get conn() this.pageStyle.conn,
// Objects returned by this actor are owned by the PageStyleActor
// to which this rule belongs.
get marshallPool() this.pageStyle,
toString: function() "[StyleRuleActor for " + this.rawRule + "]",
form: function(detail) {
if (detail === "actorid") {
return this.actorID;
}
let form = {
actor: this.actorID,
type: this.type,
line: this.line || undefined,
};
if (this.rawRule.parentRule) {
form.parentRule = this.pageStyle._styleRef(this.rawRule.parentRule).actorID;
}
if (this.rawRule.parentStyleSheet) {
form.parentStyleSheet = this.pageStyle._sheetRef(this.rawRule.parentStyleSheet).actorID;
}
switch (this.type) {
case Ci.nsIDOMCSSRule.STYLE_RULE:
form.selectors = CssLogic.getSelectors(this.rawRule);
form.cssText = this.rawStyle.cssText || "";
break;
case ELEMENT_STYLE:
// Elements don't have a parent stylesheet, and therefore
// don't have an associated URI. Provide a URI for
// those.
form.href = this.rawNode.ownerDocument.location.href;
form.cssText = this.rawStyle.cssText || "";
break;
case Ci.nsIDOMCSSRule.CHARSET_RULE:
form.encoding = this.rawRule.encoding;
break;
case Ci.nsIDOMCSSRule.IMPORT_RULE:
form.href = this.rawRule.href;
break;
case Ci.nsIDOMCSSRule.MEDIA_RULE:
form.media = [];
for (let i = 0, n = this.rawRule.media.length; i < n; i++) {
form.media.push(this.rawRule.media.item(i));
}
break;
}
return form;
},
/**
* Modify a rule's properties. Passed an array of modifications:
* {
* type: "set",
* name: <string>,
* value: <string>,
* priority: <optional string>
* }
* or
* {
* type: "remove",
* name: <string>,
* }
*
* @returns the rule with updated properties
*/
modifyProperties: method(function(modifications) {
for (let mod of modifications) {
if (mod.type === "set") {
this.rawStyle.setProperty(mod.name, mod.value, mod.priority || "");
} else if (mod.type === "remove") {
this.rawStyle.removeProperty(mod.name);
}
}
return this;
}, {
request: { modifications: Arg(0, "array:json") },
response: { rule: RetVal("domstylerule") }
})
});
/**
* Front for the StyleRule actor.
*/
var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
initialize: function(client, form, ctx, detail) {
protocol.Front.prototype.initialize.call(this, client, form, ctx, detail);
},
destroy: function() {
protocol.Front.prototype.destroy.call(this);
},
form: function(form, detail) {
if (detail === "actorid") {
this.actorID = form;
return;
}
this.actorID = form.actor;
this._form = form;
if (this._mediaText) {
this._mediaText = null;
}
},
/**
* Return a new RuleModificationList for this node.
*/
startModifyingProperties: function() {
return new RuleModificationList(this);
},
get type() this._form.type,
get line() this._form.line || -1,
get cssText() {
return this._form.cssText;
},
get selectors() {
return this._form.selectors;
},
get media() {
return this._form.media;
},
get mediaText() {
if (!this._form.media) {
return null;
}
if (this._mediaText) {
return this._mediaText;
}
this._mediaText = this.media.join(", ");
return this._mediaText;
},
get parentRule() {
return this.conn.getActor(this._form.parentRule);
},
get parentStyleSheet() {
return this.conn.getActor(this._form.parentStyleSheet);
},
get element() {
return this.conn.getActor(this._form.element);
},
get href() {
if (this._form.href) {
return this._form.href;
}
let sheet = this.parentStyleSheet;
return sheet.href || sheet.nodeHref;
},
// Only used for testing, please keep it that way.
_rawStyle: function() {
if (!this.conn._transport._serverConnection) {
console.warn("Tried to use rawNode on a remote connection.");
return null;
}
let actor = this.conn._transport._serverConnection.getActor(this.actorID);
if (!actor) {
return null;
}
return actor.rawStyle;
}
});
/**
* Convenience API for building a list of attribute modifications
* for the `modifyAttributes` request.
*/
var RuleModificationList = Class({
initialize: function(rule) {
this.rule = rule;
this.modifications = [];
},
apply: function() {
return this.rule.modifyProperties(this.modifications);
},
setProperty: function(name, value, priority) {
this.modifications.push({
type: "set",
name: name,
value: value,
priority: priority
});
},
removeProperty: function(name) {
this.modifications.push({
type: "remove",
name: name
});
}
});

Просмотреть файл

@ -14,6 +14,8 @@ include $(DEPTH)/config/autoconf.mk
MOCHITEST_CHROME_FILES = \
inspector-helpers.js \
inspector-traversal-data.html \
inspector-styles-data.html \
inspector-styles-data.css \
test_inspector-changeattrs.html \
test_inspector-changevalue.html \
test_inspector-hide.html \
@ -28,6 +30,10 @@ MOCHITEST_CHROME_FILES = \
test_inspector-retain.html \
test_inspector-pseudoclass-lock.html \
test_inspector-traversal.html \
test_styles-applied.html \
test_styles-computed.html \
test_styles-matched.html \
test_styles-modify.html \
test_unsafeDereference.html \
nonchrome_unsafeDereference.html \
$(NULL)

Просмотреть файл

@ -0,0 +1,3 @@
.external-rule {
cursor: crosshair;
}

Просмотреть файл

@ -0,0 +1,55 @@
<html>
<script>
window.onload = () => {
window.opener.postMessage('ready', '*')
}
</script>
<style>
.inheritable-rule {
font-size: 15px;
}
.uninheritable-rule {
background-color: #f06;
}
@media screen {
#mediaqueried {
background-color: #f06;
}
}
</style>
<link type="text/css" rel="stylesheet" href="inspector-styles-data.css"></link>
<body>
<h1>Style Actor Tests</h1>
<!-- Inheritance checks -->
<div id="inheritable-rule-uninheritable-style" class="inheritable-rule" style="background-color: purple">
<div id="inheritable-rule-inheritable-style" class="inheritable-rule" style="color: blue">
<div id="uninheritable-rule-uninheritable-style" class="uninheritable-rule" style="background-color: green">
<div id="uninheritable-rule-inheritable-style" class="uninheritable-rule" style="color: red">
<div id="test-node">
Here is the test node.
</div>
</div>
</div>
</div>
</div>
<!-- Computed checks -->
<div id="computed-parent" class="external-rule inheritable-rule uninheritable-rule" style="color: red;">
<div id="computed-test-node" class="external-rule">
Here is the test node.
</div>
</div>
<!-- Matched checks -->
<div id="matched-parent" class="external-rule inheritable-rule uninheritable-rule" style="color: red;">
<div id="matched-test-node" style="font-size: 10px" class="external-rule">
Here is the test node.
</div>
</div>
<div id="mediaqueried">
Screen mediaqueried.
</div>
</body>
</html>

Просмотреть файл

@ -14,7 +14,7 @@
body.appendChild(iframe);
}
</script>
<body>
<body style="background-color:white">
<h1>Inspector Actor Tests</h1>
<span id="longstring">longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong</span>
<span id="shortstring">short</span>
@ -51,4 +51,4 @@
<div id="longlist-sibling-firstchild"></div>
</div>
</body>
</html>
</html>

Просмотреть файл

@ -0,0 +1,153 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=
-->
<head>
<meta charset="utf-8">
<title>Test for Bug </title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
const promise = devtools.require("sdk/core/promise");
const inspector = devtools.require("devtools/server/actors/inspector");
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
}
var gWalker = null;
var gStyles = null;
var gClient = null;
addTest(function setup() {
let url = document.getElementById("inspectorContent").href;
attachURL(url, function(err, client, tab, doc) {
gInspectee = doc;
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
let inspector = InspectorFront(client, tab);
promiseDone(inspector.getWalker().then(walker => {
ok(walker, "getWalker() should return an actor.");
gClient = client;
gWalker = walker;
return inspector.getPageStyle();
}).then(styles => {
gStyles = styles;
}).then(runNextTest));
});
});
addTest(function inheritedUserStyles() {
let node = node;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
return gStyles.getApplied(node, { inherited: true, filter: "user" });
}).then(applied => {
ok(!applied[0].inherited, "Entry 0 should be uninherited");
is(applied[0].rule.type, 100, "Entry 0 should be an element style");
ok(!!applied[0].rule.href, "Element styles should have a URL");
is(applied[0].rule.cssText, "", "Entry 0 should be an empty style");
is(applied[1].inherited.id, "uninheritable-rule-inheritable-style",
"Entry 1 should be inherited from the parent");
is(applied[1].rule.type, 100, "Entry 1 should be an element style");
is(applied[1].rule.cssText, "color: red;", "Entry 1 should have the expected cssText");
is(applied[2].inherited.id, "inheritable-rule-inheritable-style",
"Entry 2 should be inherited from the parent's parent");
is(applied[2].rule.type, 100, "Entry 2 should be an element style");
is(applied[2].rule.cssText, "color: blue;", "Entry 2 should have the expected cssText");
is(applied[3].inherited.id, "inheritable-rule-inheritable-style",
"Entry 3 should be inherited from the parent's parent");
is(applied[3].rule.type, 1, "Entry 3 should be a rule style");
is(applied[3].rule.cssText, "font-size: 15px;", "Entry 3 should have the expected cssText");
ok(!applied[3].matchedSelectors, "Shouldn't get matchedSelectors with this request.");
is(applied[4].inherited.id, "inheritable-rule-uninheritable-style",
"Entry 4 should be inherited from the parent's parent");
is(applied[4].rule.type, 1, "Entry 4 should be an rule style");
is(applied[4].rule.cssText, "font-size: 15px;", "Entry 4 should have the expected cssText");
ok(!applied[4].matchedSelectors, "Shouldn't get matchedSelectors with this request.");
is(applied.length, 5, "Should have 5 rules.");
}).then(runNextTest));
});
addTest(function inheritedSystemStyles() {
let node = node;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
return gStyles.getApplied(node, { inherited: true, filter: "ua" });
}).then(applied => {
// If our system stylesheets are prone to churn, this might be a fragile
// test. If you're here because of that I apologize, file a bug
// and we can find a different way to test.
ok(!applied[1].inherited, "Entry 1 should not be inherited");
ok(!applied[1].rule.parentStyleSheet.system, "Entry 1 should be a system style");
is(applied[1].rule.type, 1, "Entry 1 should be a rule style");
is(applied.length, 7, "Should have 7 rules.");
}).then(runNextTest));
});
addTest(function noInheritedStyles() {
let node = node;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
return gStyles.getApplied(node, { inherited: false, filter: "user" });
}).then(applied => {
ok(!applied[0].inherited, "Entry 0 should be uninherited");
is(applied[0].rule.type, 100, "Entry 0 should be an element style");
is(applied[0].rule.cssText, "", "Entry 0 should be an empty style");
is(applied.length, 1, "Should have 1 rule.");
}).then(runNextTest));
});
addTest(function matchedSelectors() {
promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
return gStyles.getApplied(node, {
inherited: true, filter: "user", matchedSelectors: true
});
}).then(applied => {
is(applied[3].matchedSelectors[0], ".inheritable-rule", "Entry 3 should have a matched selector");
is(applied[4].matchedSelectors[0], ".inheritable-rule", "Entry 4 should have a matched selector");
}).then(runNextTest));
});
addTest(function testMediaQuery() {
let node = node;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#mediaqueried").then(node => {
return gStyles.getApplied(node, {
inherited: false, filter: "user", matchedSelectors: true
});
}).then(applied => {
is(applied[1].rule.type, 1, "Entry 1 is a rule style");
is(applied[1].rule.parentRule.type, 4, "Entry 1's parent rule is a media rule");
is(applied[1].rule.parentRule.media[0], "screen", "Entry 1's parent rule has the expected medium");
}).then(runNextTest));
});
addTest(function cleanup() {
delete gStyles;
delete gWalker;
delete gClient;
runNextTest();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,142 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=
-->
<head>
<meta charset="utf-8">
<title>Test for Bug </title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
const promise = devtools.require("sdk/core/promise");
const inspector = devtools.require("devtools/server/actors/inspector");
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
}
var gWalker = null;
var gStyles = null;
var gClient = null;
addTest(function setup() {
let url = document.getElementById("inspectorContent").href;
attachURL(url, function(err, client, tab, doc) {
gInspectee = doc;
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
let inspector = InspectorFront(client, tab);
promiseDone(inspector.getWalker().then(walker => {
ok(walker, "getWalker() should return an actor.");
gClient = client;
gWalker = walker;
return inspector.getPageStyle();
}).then(styles => {
gStyles = styles;
}).then(runNextTest));
});
});
addTest(function testComputed() {
let localNode = gInspectee.querySelector("#computed-test-node");
let elementStyle = null;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
return gStyles.getComputed(node, {});
}).then(computed => {
// Test a smattering of properties that include some system-defined
// props, some props that were defined in this node's stylesheet,
// and some default props.
is(computed["white-space"].value, "normal", "Default value should appear");
is(computed["display"].value, "block", "System stylesheet item should appear");
is(computed["cursor"].value, "crosshair", "Included stylesheet rule should appear");
is(computed["color"].value, "rgb(255, 0, 0)", "Inherited style attribute should appear");
is(computed["font-size"].value, "15px", "Inherited inline rule should appear");
// We didn't request markMatched, so these shouldn't be set
ok(!computed["cursor"].matched, "Didn't ask for matched, shouldn't get it");
ok(!computed["color"].matched, "Didn't ask for matched, shouldn't get it");
ok(!computed["font-size"].matched, "Didn't ask for matched, shouldn't get it");
}).then(runNextTest));
});
addTest(function testComputedUserMatched() {
let localNode = gInspectee.querySelector("#computed-test-node");
let elementStyle = null;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
return gStyles.getComputed(node, { filter: "user", markMatched: true });
}).then(computed => {
ok(!computed["white-space"].matched, "Default style shouldn't match");
ok(!computed["display"].matched, "Only user styles should match");
ok(computed["cursor"].matched, "Asked for matched, should get it");
ok(computed["color"].matched, "Asked for matched, should get it");
ok(computed["font-size"].matched, "Asked for matched, should get it");
}).then(runNextTest));
});
addTest(function testComputedSystemMatched() {
let localNode = gInspectee.querySelector("#computed-test-node");
let elementStyle = null;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
return gStyles.getComputed(node, { filter: "ua", markMatched: true });
}).then(computed => {
ok(!computed["white-space"].matched, "Default style shouldn't match");
ok(computed["display"].matched, "System stylesheets should match");
ok(computed["cursor"].matched, "Asked for matched, should get it");
ok(computed["color"].matched, "Asked for matched, should get it");
ok(computed["font-size"].matched, "Asked for matched, should get it");
}).then(runNextTest));
});
addTest(function testComputedUserOnlyMatched() {
let localNode = gInspectee.querySelector("#computed-test-node");
let elementStyle = null;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
return gStyles.getComputed(node, { filter: "user", onlyMatched: true });
}).then(computed => {
ok(!("white-space" in computed), "Default style shouldn't exist");
ok(!("display" in computed), "System stylesheets shouldn't exist");
ok(("cursor" in computed), "User items should exist.");
ok(("color" in computed), "User items should exist.");
ok(("font-size" in computed), "User items should exist.");
}).then(runNextTest));
});
addTest(function testComputedSystemOnlyMatched() {
let localNode = gInspectee.querySelector("#computed-test-node");
let elementStyle = null;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
return gStyles.getComputed(node, { filter: "ua", onlyMatched: true });
}).then(computed => {
ok(!("white-space" in computed), "Default style shouldn't exist");
ok(("display" in computed), "System stylesheets should exist");
ok(("cursor" in computed), "User items should exist.");
ok(("color" in computed), "User items should exist.");
ok(("font-size" in computed), "User items should exist.");
}).then(runNextTest));
});
addTest(function cleanup() {
delete gStyles;
delete gWalker;
delete gClient;
runNextTest();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,101 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=
-->
<head>
<meta charset="utf-8">
<title>Test for Bug </title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
const promise = devtools.require("sdk/core/promise");
const inspector = devtools.require("devtools/server/actors/inspector");
const {CssLogic} = devtools.require("devtools/styleinspector/css-logic");
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
}
var gWalker = null;
var gStyles = null;
var gClient = null;
addTest(function setup() {
let url = document.getElementById("inspectorContent").href;
attachURL(url, function(err, client, tab, doc) {
gInspectee = doc;
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
let inspector = InspectorFront(client, tab);
promiseDone(inspector.getWalker().then(walker => {
ok(walker, "getWalker() should return an actor.");
gClient = client;
gWalker = walker;
return inspector.getPageStyle();
}).then(styles => {
gStyles = styles;
}).then(runNextTest));
});
});
addTest(function testMatchedStyles() {
promiseDone(gWalker.querySelector(gWalker.rootNode, "#matched-test-node").then(node => {
return gStyles.getMatchedSelectors(node, "font-size", {});
}).then(matched => {
is(matched[0].sourceText, "this.style", "First match comes from the element style");
is(matched[0].selector, "@element.style", "Element style has a special selector");
is(matched[0].value, "10px", "First match has the expected value");
is(matched[0].status, CssLogic.STATUS.BEST, "First match is the best match")
is(matched[0].rule.type, 100, "First match is an element style");
is(matched[0].rule.href, gInspectee.defaultView.location.href, "Node style comes from this document")
is(matched[1].sourceText, ".inheritable-rule", "Second match comes from a rule");
is(matched[1].selector, ".inheritable-rule", "Second style has a selector");
is(matched[1].value, "15px", "Second match has the expected value");
is(matched[1].status, CssLogic.STATUS.PARENT_MATCH, "Second match is from the parent")
is(matched[1].rule.parentStyleSheet.href, null, "Inline stylesheet shouldn't have an href");
is(matched[1].rule.parentStyleSheet.nodeHref, gInspectee.defaultView.location.href, "Inline stylesheet's nodeHref should match the current document");
ok(!matched[1].rule.parentStyleSheet.system, "Inline stylesheet shouldn't be a system stylesheet.");
}).then(runNextTest));
});
addTest(function testSystemStyles() {
let testNode = null;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#matched-test-node").then(node => {
testNode = node;
return gStyles.getMatchedSelectors(testNode, "display", { filter: "user" });
}).then(matched => {
is(matched.length, 0, "No user selectors apply to this rule.");
return gStyles.getMatchedSelectors(testNode, "display", { filter: "ua" });
}).then(matched => {
is(matched[0].selector, "div", "Should match system div selector");
is(matched[0].value, "block");
}).then(runNextTest));
});
addTest(function cleanup() {
delete gStyles;
delete gWalker;
delete gClient;
runNextTest();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,103 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=
-->
<head>
<meta charset="utf-8">
<title>Test for Bug </title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
const promise = devtools.require("sdk/core/promise");
const inspector = devtools.require("devtools/server/actors/inspector");
window.onload = function() {
SimpleTest.waitForExplicitFinish();
runNextTest();
}
var gWalker = null;
var gStyles = null;
var gClient = null;
addTest(function setup() {
let url = document.getElementById("inspectorContent").href;
attachURL(url, function(err, client, tab, doc) {
gInspectee = doc;
let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
let inspector = InspectorFront(client, tab);
promiseDone(inspector.getWalker().then(walker => {
ok(walker, "getWalker() should return an actor.");
gClient = client;
gWalker = walker;
return inspector.getPageStyle();
}).then(styles => {
gStyles = styles;
}).then(runNextTest));
});
});
addTest(function modifyProperties() {
let localNode = gInspectee.querySelector("#inheritable-rule-inheritable-style");
let elementStyle = null;
promiseDone(gWalker.querySelector(gWalker.rootNode, "#inheritable-rule-inheritable-style").then(node => {
return gStyles.getApplied(node, { inherited: false, filter: "user" });
}).then(applied => {
elementStyle = applied[0].rule;
is(elementStyle.cssText, localNode.style.cssText, "Got expected css text");
// Will start with "color:blue"
let changes = elementStyle.startModifyingProperties();
// Change an existing property...
changes.setProperty("color", "black");
// Create a new property
changes.setProperty("background-color", "green");
// Create a new property and then change it immediately.
changes.setProperty("border", "1px solid black");
changes.setProperty("border", "2px solid black");
return changes.apply();
}).then(() => {
is(elementStyle.cssText, "color: black; background-color: green; border: 2px solid black;", "Should have expected cssText");
is(elementStyle.cssText, localNode.style.cssText, "Local node and style front match.");
// Remove all the properties
let changes = elementStyle.startModifyingProperties();
changes.removeProperty("color");
changes.removeProperty("background-color");
changes.removeProperty("border");
return changes.apply();
}).then(() => {
is(elementStyle.cssText, "", "Should have expected cssText");
is(elementStyle.cssText, localNode.style.cssText, "Local node and style front match.");
}).then(runNextTest));
});
addTest(function cleanup() {
delete gStyles;
delete gWalker;
delete gClient;
runNextTest();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

Просмотреть файл

@ -63,8 +63,9 @@ exports.CssLogic = CssLogic;
* Special values for filter, in addition to an href these values can be used
*/
CssLogic.FILTER = {
ALL: "all", // show properties from all user style sheets.
UA: "ua", // ALL, plus user-agent (i.e. browser) style sheets
ALL: "user", // show properties from all user style sheets
USER: "user",
UA: "ua", // ALL, plus user-agent (i.e. browser) style sheets
};
/**