2013-07-23 22:51:58 +00:00
|
|
|
/* 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/. */
|
2015-06-17 09:50:28 +00:00
|
|
|
/* globals CssLogic, DOMUtils, CSS */
|
2013-07-23 22:51:58 +00:00
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
2014-03-27 18:42:13 +00:00
|
|
|
const {Cc, Ci, Cu} = require("chrome");
|
2015-08-26 13:05:13 +00:00
|
|
|
const promise = require("promise");
|
2013-07-23 22:51:58 +00:00
|
|
|
const protocol = require("devtools/server/protocol");
|
|
|
|
const {Arg, Option, method, RetVal, types} = protocol;
|
|
|
|
const events = require("sdk/event/core");
|
2014-11-25 15:36:44 +00:00
|
|
|
const {Class} = require("sdk/core/heritage");
|
|
|
|
const {LongStringActor} = require("devtools/server/actors/string");
|
2015-06-05 18:48:00 +00:00
|
|
|
const {PSEUDO_ELEMENT_SET} = require("devtools/styleinspector/css-logic");
|
2014-08-22 19:30:00 +00:00
|
|
|
|
|
|
|
// This will add the "stylesheet" actor type for protocol.js to recognize
|
|
|
|
require("devtools/server/actors/stylesheets");
|
2013-07-23 22:51:58 +00:00
|
|
|
|
2015-06-17 09:50:28 +00:00
|
|
|
loader.lazyGetter(this, "CssLogic", () => {
|
|
|
|
return require("devtools/styleinspector/css-logic").CssLogic;
|
|
|
|
});
|
|
|
|
loader.lazyGetter(this, "DOMUtils", () => {
|
|
|
|
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
|
|
|
});
|
2013-07-23 22:51:58 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2015-06-05 18:48:00 +00:00
|
|
|
// Not included since these are uneditable by the user.
|
|
|
|
// See https://hg.mozilla.org/mozilla-central/file/696a4ad5d011/layout/style/nsCSSPseudoElementList.h#l74
|
|
|
|
PSEUDO_ELEMENT_SET.delete(":-moz-meter-bar");
|
|
|
|
PSEUDO_ELEMENT_SET.delete(":-moz-list-bullet");
|
|
|
|
PSEUDO_ELEMENT_SET.delete(":-moz-list-number");
|
|
|
|
PSEUDO_ELEMENT_SET.delete(":-moz-focus-inner");
|
|
|
|
PSEUDO_ELEMENT_SET.delete(":-moz-focus-outer");
|
|
|
|
PSEUDO_ELEMENT_SET.delete(":-moz-math-anonymous");
|
|
|
|
PSEUDO_ELEMENT_SET.delete(":-moz-math-stretchy");
|
|
|
|
|
|
|
|
const PSEUDO_ELEMENTS = Array.from(PSEUDO_ELEMENT_SET);
|
|
|
|
|
2013-09-04 15:43:40 +00:00
|
|
|
exports.PSEUDO_ELEMENTS = PSEUDO_ELEMENTS;
|
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
// When gathering rules to read for pseudo elements, we will skip
|
|
|
|
// :before and :after, which are handled as a special case.
|
|
|
|
const PSEUDO_ELEMENTS_TO_READ = PSEUDO_ELEMENTS.filter(pseudo => {
|
|
|
|
return pseudo !== ":before" && pseudo !== ":after";
|
|
|
|
});
|
|
|
|
|
2014-11-25 15:36:44 +00:00
|
|
|
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
|
|
|
const FONT_PREVIEW_TEXT = "Abc";
|
|
|
|
const FONT_PREVIEW_FONT_SIZE = 40;
|
|
|
|
const FONT_PREVIEW_FILLSTYLE = "black";
|
2014-12-17 15:05:00 +00:00
|
|
|
const NORMAL_FONT_WEIGHT = 400;
|
|
|
|
const BOLD_FONT_WEIGHT = 700;
|
2015-06-17 09:50:28 +00:00
|
|
|
// Offset (in px) to avoid cutting off text edges of italic fonts.
|
|
|
|
const FONT_PREVIEW_OFFSET = 4;
|
2014-11-25 15:36:44 +00:00
|
|
|
|
2013-07-23 22:51:58 +00:00
|
|
|
// Predeclare the domnode actor type for use in requests.
|
|
|
|
types.addActorType("domnode");
|
|
|
|
|
2014-07-07 07:18:00 +00:00
|
|
|
// Predeclare the domstylerule actor type
|
|
|
|
types.addActorType("domstylerule");
|
|
|
|
|
2013-07-23 22:51:58 +00:00
|
|
|
/**
|
|
|
|
* 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).
|
2015-05-28 00:36:17 +00:00
|
|
|
*
|
|
|
|
* Note appliedstyle only sends the list of actorIDs and is not a valid return
|
|
|
|
* value on its own. appliedstyle should be returned with the actual list of
|
|
|
|
* StyleRuleActor and StyleSheetActor. See appliedStylesReturn.
|
2013-07-23 22:51:58 +00:00
|
|
|
*/
|
|
|
|
types.addDictType("appliedstyle", {
|
|
|
|
rule: "domstylerule#actorid",
|
2014-07-20 03:08:43 +00:00
|
|
|
inherited: "nullable:domnode#actorid",
|
|
|
|
keyframes: "nullable:domstylerule#actorid"
|
2013-07-23 22:51:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
types.addDictType("matchedselector", {
|
|
|
|
rule: "domstylerule#actorid",
|
|
|
|
selector: "string",
|
|
|
|
value: "string",
|
|
|
|
status: "number"
|
|
|
|
});
|
|
|
|
|
2014-07-07 07:18:00 +00:00
|
|
|
types.addDictType("appliedStylesReturn", {
|
|
|
|
entries: "array:appliedstyle",
|
|
|
|
rules: "array:domstylerule",
|
|
|
|
sheets: "array:stylesheet"
|
|
|
|
});
|
|
|
|
|
2015-05-28 00:36:17 +00:00
|
|
|
types.addDictType("modifiedStylesReturn", {
|
|
|
|
isMatching: RetVal("boolean"),
|
|
|
|
ruleProps: RetVal("nullable:appliedStylesReturn")
|
|
|
|
});
|
|
|
|
|
2014-11-25 15:36:44 +00:00
|
|
|
types.addDictType("fontpreview", {
|
|
|
|
data: "nullable:longstring",
|
|
|
|
size: "json"
|
|
|
|
});
|
|
|
|
|
|
|
|
types.addDictType("fontface", {
|
|
|
|
name: "string",
|
|
|
|
CSSFamilyName: "string",
|
|
|
|
rule: "nullable:domstylerule",
|
|
|
|
srcIndex: "number",
|
|
|
|
URI: "string",
|
|
|
|
format: "string",
|
|
|
|
preview: "nullable:fontpreview",
|
|
|
|
localName: "string",
|
|
|
|
metadata: "string"
|
|
|
|
});
|
|
|
|
|
2013-07-23 22:51:58 +00:00
|
|
|
/**
|
|
|
|
* The PageStyle actor lets the client look at the styles on a page, as
|
|
|
|
* they are applied to a given node.
|
|
|
|
*/
|
2015-06-17 09:50:28 +00:00
|
|
|
let PageStyleActor = protocol.ActorClass({
|
2013-07-23 22:51:58 +00:00
|
|
|
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;
|
2015-06-17 09:50:28 +00:00
|
|
|
this.cssLogic = new CssLogic();
|
2013-07-23 22:51:58 +00:00
|
|
|
|
|
|
|
// Stores the association of DOM objects -> actors
|
2015-06-17 09:50:28 +00:00
|
|
|
this.refMap = new Map();
|
2014-07-07 07:18:00 +00:00
|
|
|
|
|
|
|
this.onFrameUnload = this.onFrameUnload.bind(this);
|
|
|
|
events.on(this.inspector.tabActor, "will-navigate", this.onFrameUnload);
|
2013-07-23 22:51:58 +00:00
|
|
|
},
|
|
|
|
|
2015-05-28 15:54:00 +00:00
|
|
|
get conn() {
|
|
|
|
return this.inspector.conn;
|
|
|
|
},
|
2013-07-23 22:51:58 +00:00
|
|
|
|
2014-12-10 08:09:19 +00:00
|
|
|
form: function(detail) {
|
|
|
|
if (detail === "actorid") {
|
|
|
|
return this.actorID;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
actor: this.actorID,
|
|
|
|
traits: {
|
|
|
|
// Whether the actor has had bug 1103993 fixed, which means that the
|
|
|
|
// getApplied method calls cssLogic.highlight(node) to recreate the
|
|
|
|
// style cache. Clients requesting getApplied from actors that have not
|
|
|
|
// been fixed must make sure cssLogic.highlight(node) was called before.
|
|
|
|
getAppliedCreatesStyleCache: true
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2013-07-23 22:51:58 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2014-08-22 19:30:00 +00:00
|
|
|
* Return or create a StyleSheetActor for the given nsIDOMCSSStyleSheet.
|
|
|
|
* @param {DOMStyleSheet} sheet
|
|
|
|
* The style sheet to create an actor for.
|
|
|
|
* @return {StyleSheetActor}
|
|
|
|
* The actor for this style sheet
|
2013-07-23 22:51:58 +00:00
|
|
|
*/
|
|
|
|
_sheetRef: function(sheet) {
|
2014-08-22 19:30:00 +00:00
|
|
|
let tabActor = this.inspector.tabActor;
|
|
|
|
let actor = tabActor.createStyleSheetActor(sheet);
|
2013-07-23 22:51:58 +00:00
|
|
|
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 ret = Object.create(null);
|
|
|
|
|
|
|
|
this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
|
|
|
|
this.cssLogic.highlight(node.rawNode);
|
2014-07-20 03:08:43 +00:00
|
|
|
let computed = this.cssLogic.computedStyle || [];
|
2013-07-23 22:51:58 +00:00
|
|
|
|
|
|
|
Array.prototype.forEach.call(computed, name => {
|
|
|
|
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]) {
|
2015-06-17 09:50:28 +00:00
|
|
|
ret[key].matched = options.markMatched ? true : undefined;
|
2013-07-23 22:51:58 +00:00
|
|
|
} 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")
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
|
2015-01-05 17:38:57 +00:00
|
|
|
/**
|
|
|
|
* Get all the fonts from a page.
|
|
|
|
*
|
|
|
|
* @param object options
|
|
|
|
* `includePreviews`: Whether to also return image previews of the fonts.
|
|
|
|
* `previewText`: The text to display in the previews.
|
|
|
|
* `previewFontSize`: The font size of the text in the previews.
|
|
|
|
*
|
|
|
|
* @returns object
|
|
|
|
* object with 'fontFaces', a list of fonts that apply to this node.
|
|
|
|
*/
|
|
|
|
getAllUsedFontFaces: method(function(options) {
|
|
|
|
let windows = this.inspector.tabActor.windows;
|
|
|
|
let fontsList = [];
|
2015-06-17 09:50:28 +00:00
|
|
|
for (let win of windows) {
|
2015-01-05 17:38:57 +00:00
|
|
|
fontsList = [...fontsList,
|
|
|
|
...this.getUsedFontFaces(win.document.body, options)];
|
|
|
|
}
|
|
|
|
return fontsList;
|
2015-06-17 09:50:28 +00:00
|
|
|
}, {
|
2015-01-05 17:38:57 +00:00
|
|
|
request: {
|
|
|
|
includePreviews: Option(0, "boolean"),
|
|
|
|
previewText: Option(0, "string"),
|
|
|
|
previewFontSize: Option(0, "string"),
|
|
|
|
previewFillStyle: Option(0, "string")
|
|
|
|
},
|
|
|
|
response: {
|
|
|
|
fontFaces: RetVal("array:fontface")
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
|
2014-11-25 15:36:44 +00:00
|
|
|
/**
|
|
|
|
* Get the font faces used in an element.
|
|
|
|
*
|
2015-01-05 17:38:57 +00:00
|
|
|
* @param NodeActor node / actual DOM node
|
2014-11-25 15:36:44 +00:00
|
|
|
* The node to get fonts from.
|
|
|
|
* @param object options
|
|
|
|
* `includePreviews`: Whether to also return image previews of the fonts.
|
|
|
|
* `previewText`: The text to display in the previews.
|
|
|
|
* `previewFontSize`: The font size of the text in the previews.
|
|
|
|
*
|
|
|
|
* @returns object
|
|
|
|
* object with 'fontFaces', a list of fonts that apply to this node.
|
|
|
|
*/
|
|
|
|
getUsedFontFaces: method(function(node, options) {
|
2015-01-05 17:38:57 +00:00
|
|
|
// node.rawNode is defined for NodeActor objects
|
|
|
|
let actualNode = node.rawNode || node;
|
|
|
|
let contentDocument = actualNode.ownerDocument;
|
2014-11-25 15:36:44 +00:00
|
|
|
// We don't get fonts for a node, but for a range
|
|
|
|
let rng = contentDocument.createRange();
|
2015-01-05 17:38:57 +00:00
|
|
|
rng.selectNodeContents(actualNode);
|
2014-11-25 15:36:44 +00:00
|
|
|
let fonts = DOMUtils.getUsedFontFaces(rng);
|
|
|
|
let fontsArray = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < fonts.length; i++) {
|
|
|
|
let font = fonts.item(i);
|
|
|
|
let fontFace = {
|
|
|
|
name: font.name,
|
|
|
|
CSSFamilyName: font.CSSFamilyName,
|
|
|
|
srcIndex: font.srcIndex,
|
|
|
|
URI: font.URI,
|
|
|
|
format: font.format,
|
|
|
|
localName: font.localName,
|
|
|
|
metadata: font.metadata
|
2015-06-17 09:50:28 +00:00
|
|
|
};
|
2014-11-25 15:36:44 +00:00
|
|
|
|
|
|
|
// If this font comes from a @font-face rule
|
|
|
|
if (font.rule) {
|
2015-04-29 09:43:38 +00:00
|
|
|
fontFace.rule = StyleRuleActor(this, font.rule);
|
2014-11-25 15:36:44 +00:00
|
|
|
fontFace.ruleText = font.rule.cssText;
|
|
|
|
}
|
|
|
|
|
2014-12-17 15:05:00 +00:00
|
|
|
// Get the weight and style of this font for the preview and sort order
|
|
|
|
let weight = NORMAL_FONT_WEIGHT, style = "";
|
|
|
|
if (font.rule) {
|
|
|
|
weight = font.rule.style.getPropertyValue("font-weight")
|
|
|
|
|| NORMAL_FONT_WEIGHT;
|
|
|
|
if (weight == "bold") {
|
|
|
|
weight = BOLD_FONT_WEIGHT;
|
|
|
|
} else if (weight == "normal") {
|
|
|
|
weight = NORMAL_FONT_WEIGHT;
|
|
|
|
}
|
|
|
|
style = font.rule.style.getPropertyValue("font-style") || "";
|
|
|
|
}
|
|
|
|
fontFace.weight = weight;
|
|
|
|
fontFace.style = style;
|
|
|
|
|
2014-11-25 15:36:44 +00:00
|
|
|
if (options.includePreviews) {
|
|
|
|
let opts = {
|
|
|
|
previewText: options.previewText,
|
|
|
|
previewFontSize: options.previewFontSize,
|
2014-12-17 15:05:00 +00:00
|
|
|
fontStyle: weight + " " + style,
|
2014-11-25 15:36:44 +00:00
|
|
|
fillStyle: options.previewFillStyle
|
2015-06-17 09:50:28 +00:00
|
|
|
};
|
2014-11-25 15:36:44 +00:00
|
|
|
let { dataURL, size } = getFontPreviewData(font.CSSFamilyName,
|
|
|
|
contentDocument, opts);
|
|
|
|
fontFace.preview = {
|
|
|
|
data: LongStringActor(this.conn, dataURL),
|
|
|
|
size: size
|
|
|
|
};
|
|
|
|
}
|
|
|
|
fontsArray.push(fontFace);
|
|
|
|
}
|
|
|
|
|
|
|
|
// @font-face fonts at the top, then alphabetically, then by weight
|
2014-12-17 15:05:00 +00:00
|
|
|
fontsArray.sort(function(a, b) {
|
|
|
|
return a.weight > b.weight ? 1 : -1;
|
|
|
|
});
|
2014-11-25 15:36:44 +00:00
|
|
|
fontsArray.sort(function(a, b) {
|
|
|
|
if (a.CSSFamilyName == b.CSSFamilyName) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return a.CSSFamilyName > b.CSSFamilyName ? 1 : -1;
|
|
|
|
});
|
|
|
|
fontsArray.sort(function(a, b) {
|
|
|
|
if ((a.rule && b.rule) || (!a.rule && !b.rule)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return !a.rule && b.rule ? 1 : -1;
|
|
|
|
});
|
|
|
|
|
|
|
|
return fontsArray;
|
|
|
|
}, {
|
|
|
|
request: {
|
|
|
|
node: Arg(0, "domnode"),
|
|
|
|
includePreviews: Option(1, "boolean"),
|
|
|
|
previewText: Option(1, "string"),
|
|
|
|
previewFontSize: Option(1, "string"),
|
|
|
|
previewFillStyle: Option(1, "string")
|
|
|
|
},
|
|
|
|
response: {
|
|
|
|
fontFaces: RetVal("array:fontface")
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
|
2013-07-23 22:51:58 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
|
2015-06-17 09:50:28 +00:00
|
|
|
let rules = new Set();
|
|
|
|
let sheets = new Set();
|
2013-07-23 22:51:58 +00:00
|
|
|
|
|
|
|
let matched = [];
|
|
|
|
let propInfo = this.cssLogic.getPropertyInfo(property);
|
|
|
|
for (let selectorInfo of propInfo.matchedSelectors) {
|
2013-08-07 16:47:45 +00:00
|
|
|
let cssRule = selectorInfo.selector.cssRule;
|
|
|
|
let domRule = cssRule.sourceElement || cssRule.domRule;
|
2013-07-23 22:51:58 +00:00
|
|
|
|
|
|
|
let rule = this._styleRef(domRule);
|
|
|
|
rules.add(rule);
|
|
|
|
|
|
|
|
matched.push({
|
|
|
|
rule: rule,
|
|
|
|
sourceText: this.getSelectorSource(selectorInfo, node.rawNode),
|
|
|
|
selector: selectorInfo.selector.text,
|
2013-10-18 14:01:20 +00:00
|
|
|
name: selectorInfo.property,
|
2013-07-23 22:51:58 +00:00
|
|
|
value: selectorInfo.value,
|
|
|
|
status: selectorInfo.status
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this.expandSets(rules, sheets);
|
|
|
|
|
|
|
|
return {
|
|
|
|
matched: matched,
|
|
|
|
rules: [...rules],
|
2015-06-17 09:50:28 +00:00
|
|
|
sheets: [...sheets]
|
2013-10-18 14:01:20 +00:00
|
|
|
};
|
2013-07-23 22:51:58 +00:00
|
|
|
}, {
|
|
|
|
request: {
|
|
|
|
node: Arg(0, "domnode"),
|
|
|
|
property: Arg(1, "string"),
|
|
|
|
filter: Option(2, "string")
|
|
|
|
},
|
|
|
|
response: RetVal(types.addDictType("matchedselectorresponse", {
|
|
|
|
rules: "array:domstylerule",
|
2013-12-07 07:52:32 +00:00
|
|
|
sheets: "array:stylesheet",
|
2013-07-23 22:51:58 +00:00
|
|
|
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);
|
|
|
|
}
|
2015-06-17 09:50:28 +00:00
|
|
|
result += ".style";
|
2013-07-23 22:51:58 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the set of styles that apply to a given 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'
|
|
|
|
* `inherited`: Include styles inherited from parent nodes.
|
2015-06-05 18:48:00 +00:00
|
|
|
* `matchedSelectors`: Include an array of specific selectors that
|
2013-07-23 22:51:58 +00:00
|
|
|
* caused this rule to match its node.
|
|
|
|
*/
|
|
|
|
getApplied: method(function(node, options) {
|
2015-04-17 10:09:58 +00:00
|
|
|
if (!node) {
|
|
|
|
return {entries: [], rules: [], sheets: []};
|
|
|
|
}
|
|
|
|
|
2014-12-10 08:09:19 +00:00
|
|
|
this.cssLogic.highlight(node.rawNode);
|
2013-07-23 22:51:58 +00:00
|
|
|
let entries = [];
|
2014-09-29 07:29:00 +00:00
|
|
|
entries = entries.concat(this._getAllElementRules(node, undefined, options));
|
2014-07-07 07:18:00 +00:00
|
|
|
return this.getAppliedProps(node, entries, options);
|
2013-07-23 22:51:58 +00:00
|
|
|
}, {
|
|
|
|
request: {
|
|
|
|
node: Arg(0, "domnode"),
|
|
|
|
inherited: Option(1, "boolean"),
|
|
|
|
matchedSelectors: Option(1, "boolean"),
|
|
|
|
filter: Option(1, "string")
|
|
|
|
},
|
2014-07-07 07:18:00 +00:00
|
|
|
response: RetVal("appliedStylesReturn")
|
2013-07-23 22:51:58 +00:00
|
|
|
}),
|
|
|
|
|
|
|
|
_hasInheritedProps: function(style) {
|
|
|
|
return Array.prototype.some.call(style, prop => {
|
|
|
|
return DOMUtils.isInheritedProperty(prop);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2014-09-29 07:29:00 +00:00
|
|
|
* Helper function for getApplied, gets all the rules from a given
|
|
|
|
* element. See getApplied for documentation on parameters.
|
|
|
|
* @param NodeActor node
|
|
|
|
* @param bool inherited
|
|
|
|
* @param object options
|
|
|
|
|
|
|
|
* @return Array The rules for a given element. Each item in the
|
|
|
|
* array has the following signature:
|
|
|
|
* - rule RuleActor
|
|
|
|
* - isSystem Boolean
|
|
|
|
* - inherited Boolean
|
|
|
|
* - pseudoElement String
|
2013-07-23 22:51:58 +00:00
|
|
|
*/
|
2014-09-29 07:29:00 +00:00
|
|
|
_getAllElementRules: function(node, inherited, options) {
|
|
|
|
let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node.rawNode);
|
|
|
|
let rules = [];
|
|
|
|
|
|
|
|
if (!bindingElement || !bindingElement.style) {
|
|
|
|
return rules;
|
2014-02-12 11:14:46 +00:00
|
|
|
}
|
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
let elementStyle = this._styleRef(bindingElement);
|
|
|
|
let showElementStyles = !inherited && !pseudo;
|
2015-06-17 09:50:28 +00:00
|
|
|
let showInheritedStyles = inherited &&
|
|
|
|
this._hasInheritedProps(bindingElement.style);
|
2013-07-23 22:51:58 +00:00
|
|
|
|
2015-06-05 18:48:00 +00:00
|
|
|
let rule = {
|
|
|
|
rule: elementStyle,
|
|
|
|
pseudoElement: null,
|
|
|
|
isSystem: false,
|
|
|
|
inherited: false
|
|
|
|
};
|
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
// First any inline styles
|
|
|
|
if (showElementStyles) {
|
2015-06-05 18:48:00 +00:00
|
|
|
rules.push(rule);
|
2013-07-23 22:51:58 +00:00
|
|
|
}
|
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
// Now any inherited styles
|
|
|
|
if (showInheritedStyles) {
|
2015-06-05 18:48:00 +00:00
|
|
|
rule.inherited = inherited;
|
|
|
|
rules.push(rule);
|
2014-09-29 07:29:00 +00:00
|
|
|
}
|
2013-07-23 22:51:58 +00:00
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
// Add normal rules. Typically this is passing in the node passed into the
|
|
|
|
// function, unless if that node was ::before/::after. In which case,
|
|
|
|
// it will pass in the parentNode along with "::before"/"::after".
|
2015-06-17 09:50:28 +00:00
|
|
|
this._getElementRules(bindingElement, pseudo, inherited, options).forEach(rule => {
|
2014-09-29 07:29:00 +00:00
|
|
|
// The only case when there would be a pseudo here is ::before/::after,
|
|
|
|
// and in this case we want to tell the view that it belongs to the
|
|
|
|
// element (which is a _moz_generated_content native anonymous element).
|
|
|
|
rule.pseudoElement = null;
|
|
|
|
rules.push(rule);
|
|
|
|
});
|
2013-07-23 22:51:58 +00:00
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
// Now any pseudos (except for ::before / ::after, which was handled as
|
|
|
|
// a 'normal rule' above.
|
|
|
|
if (showElementStyles) {
|
|
|
|
for (let pseudo of PSEUDO_ELEMENTS_TO_READ) {
|
2015-06-17 09:50:28 +00:00
|
|
|
this._getElementRules(bindingElement, pseudo, inherited, options).forEach(rule => {
|
2014-09-29 07:29:00 +00:00
|
|
|
rules.push(rule);
|
|
|
|
});
|
2013-07-23 22:51:58 +00:00
|
|
|
}
|
2014-09-29 07:29:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return rules;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function for _getAllElementRules, returns the rules from a given
|
|
|
|
* element. See getApplied for documentation on parameters.
|
|
|
|
* @param DOMNode node
|
|
|
|
* @param string pseudo
|
|
|
|
* @param DOMNode inherited
|
|
|
|
* @param object options
|
|
|
|
*
|
|
|
|
* @returns Array
|
|
|
|
*/
|
2015-06-17 09:50:28 +00:00
|
|
|
_getElementRules: function(node, pseudo, inherited, options) {
|
2014-09-29 07:29:00 +00:00
|
|
|
let domRules = DOMUtils.getCSSStyleRules(node, pseudo);
|
|
|
|
if (!domRules) {
|
|
|
|
return [];
|
|
|
|
}
|
2013-07-23 22:51:58 +00:00
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
let rules = [];
|
2013-09-04 15:43:40 +00:00
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
// getCSSStyleRules returns ordered from least-specific to
|
|
|
|
// most-specific.
|
|
|
|
for (let i = domRules.Count() - 1; i >= 0; i--) {
|
|
|
|
let domRule = domRules.GetElementAt(i);
|
2013-09-04 15:43:40 +00:00
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
let isSystem = !CssLogic.isContentStylesheet(domRule.parentStyleSheet);
|
2013-09-04 15:43:40 +00:00
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
if (isSystem && options.filter != CssLogic.FILTER.UA) {
|
|
|
|
continue;
|
|
|
|
}
|
2013-09-04 15:43:40 +00:00
|
|
|
|
2014-09-29 07:29:00 +00:00
|
|
|
if (inherited) {
|
|
|
|
// Don't include inherited rules if none of its properties
|
|
|
|
// are inheritable.
|
|
|
|
let hasInherited = [...domRule.style].some(
|
|
|
|
prop => DOMUtils.isInheritedProperty(prop)
|
|
|
|
);
|
|
|
|
if (!hasInherited) {
|
|
|
|
continue;
|
|
|
|
}
|
2013-07-23 22:51:58 +00:00
|
|
|
}
|
2014-09-29 07:29:00 +00:00
|
|
|
|
|
|
|
let ruleActor = this._styleRef(domRule);
|
|
|
|
rules.push({
|
|
|
|
rule: ruleActor,
|
|
|
|
inherited: inherited,
|
|
|
|
isSystem: isSystem,
|
|
|
|
pseudoElement: pseudo
|
|
|
|
});
|
2013-07-23 22:51:58 +00:00
|
|
|
}
|
2014-09-29 07:29:00 +00:00
|
|
|
return rules;
|
2013-07-23 22:51:58 +00:00
|
|
|
},
|
|
|
|
|
2014-07-07 07:18:00 +00:00
|
|
|
/**
|
2015-05-28 00:36:17 +00:00
|
|
|
* Helper function for getApplied that fetches a set of style properties that
|
|
|
|
* apply to the given node and associated rules
|
2014-07-07 07:18:00 +00:00
|
|
|
* @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'
|
|
|
|
* `inherited`: Include styles inherited from parent nodes.
|
|
|
|
* `matchedSeletors`: Include an array of specific selectors that
|
|
|
|
* caused this rule to match its node.
|
|
|
|
* @param array entries
|
|
|
|
* List of appliedstyle objects that lists the rules that apply to the
|
|
|
|
* node. If adding a new rule to the stylesheet, only the new rule entry
|
|
|
|
* is provided and only the style properties that apply to the new
|
|
|
|
* rule is fetched.
|
|
|
|
* @returns Object containing the list of rule entries, rule actors and
|
|
|
|
* stylesheet actors that applies to the given node and its associated
|
|
|
|
* rules.
|
|
|
|
*/
|
|
|
|
getAppliedProps: function(node, entries, options) {
|
|
|
|
if (options.inherited) {
|
|
|
|
let parent = this.walker.parentNode(node);
|
|
|
|
while (parent && parent.rawNode.nodeType != Ci.nsIDOMNode.DOCUMENT_NODE) {
|
2014-09-29 07:29:00 +00:00
|
|
|
entries = entries.concat(this._getAllElementRules(parent, parent, options));
|
2014-07-07 07:18:00 +00:00
|
|
|
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;
|
2014-09-29 07:29:00 +00:00
|
|
|
|
2015-06-17 09:50:28 +00:00
|
|
|
let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(element);
|
2014-07-07 07:18:00 +00:00
|
|
|
entry.matchedSelectors = [];
|
|
|
|
for (let i = 0; i < selectors.length; i++) {
|
2014-09-29 07:29:00 +00:00
|
|
|
if (DOMUtils.selectorMatchesElement(bindingElement, domRule, i, pseudo)) {
|
2014-07-07 07:18:00 +00:00
|
|
|
entry.matchedSelectors.push(selectors[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-20 03:08:43 +00:00
|
|
|
// Add all the keyframes rule associated with the element
|
|
|
|
let computedStyle = this.cssLogic.computedStyle;
|
|
|
|
if (computedStyle) {
|
|
|
|
let animationNames = computedStyle.animationName.split(",");
|
|
|
|
animationNames = animationNames.map(name => name.trim());
|
|
|
|
|
|
|
|
if (animationNames) {
|
|
|
|
// Traverse through all the available keyframes rule and add
|
|
|
|
// the keyframes rule that matches the computed animation name
|
|
|
|
for (let keyframesRule of this.cssLogic.keyframesRules) {
|
|
|
|
if (animationNames.indexOf(keyframesRule.name) > -1) {
|
|
|
|
for (let rule of keyframesRule.cssRules) {
|
|
|
|
entries.push({
|
|
|
|
rule: this._styleRef(rule),
|
|
|
|
keyframes: this._styleRef(keyframesRule)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-17 09:50:28 +00:00
|
|
|
let rules = new Set();
|
|
|
|
let sheets = new Set();
|
2014-07-07 07:18:00 +00:00
|
|
|
entries.forEach(entry => rules.add(entry.rule));
|
|
|
|
this.expandSets(rules, sheets);
|
|
|
|
|
|
|
|
return {
|
|
|
|
entries: entries,
|
|
|
|
rules: [...rules],
|
|
|
|
sheets: [...sheets]
|
2015-06-17 09:50:28 +00:00
|
|
|
};
|
2014-07-07 07:18:00 +00:00
|
|
|
},
|
|
|
|
|
2013-07-23 22:51:58 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-08-12 14:43:19 +00:00
|
|
|
},
|
|
|
|
|
2015-06-18 10:28:12 +00:00
|
|
|
/**
|
|
|
|
* Get layout-related information about a node.
|
|
|
|
* This method returns an object with properties giving information about
|
|
|
|
* the node's margin, border, padding and content region sizes, as well
|
|
|
|
* as information about the type of box, its position, z-index, etc...
|
|
|
|
* @param {NodeActor} node
|
|
|
|
* @param {Object} options The only available option is autoMargins.
|
|
|
|
* If set to true, the element's margins will receive an extra check to see
|
|
|
|
* whether they are set to "auto" (knowing that the computed-style in this
|
|
|
|
* case would return "0px").
|
|
|
|
* The returned object will contain an extra property (autoMargins) listing
|
|
|
|
* all margins that are set to auto, e.g. {top: "auto", left: "auto"}.
|
|
|
|
* @return {Object}
|
|
|
|
*/
|
2013-08-12 14:43:19 +00:00
|
|
|
getLayout: method(function(node, options) {
|
|
|
|
this.cssLogic.highlight(node.rawNode);
|
|
|
|
|
|
|
|
let layout = {};
|
|
|
|
|
|
|
|
// First, we update the first part of the layout view, with
|
|
|
|
// the size of the element.
|
|
|
|
|
|
|
|
let clientRect = node.rawNode.getBoundingClientRect();
|
2015-02-24 08:23:00 +00:00
|
|
|
layout.width = parseFloat(clientRect.width.toPrecision(6));
|
|
|
|
layout.height = parseFloat(clientRect.height.toPrecision(6));
|
2013-08-12 14:43:19 +00:00
|
|
|
|
|
|
|
// We compute and update the values of margins & co.
|
2014-09-29 07:29:00 +00:00
|
|
|
let style = CssLogic.getComputedStyle(node.rawNode);
|
2013-08-12 14:43:19 +00:00
|
|
|
for (let prop of [
|
2013-11-19 14:16:03 +00:00
|
|
|
"position",
|
2013-08-12 14:43:19 +00:00
|
|
|
"margin-top",
|
|
|
|
"margin-right",
|
|
|
|
"margin-bottom",
|
|
|
|
"margin-left",
|
|
|
|
"padding-top",
|
|
|
|
"padding-right",
|
|
|
|
"padding-bottom",
|
|
|
|
"padding-left",
|
|
|
|
"border-top-width",
|
|
|
|
"border-right-width",
|
|
|
|
"border-bottom-width",
|
2015-06-18 10:28:12 +00:00
|
|
|
"border-left-width",
|
|
|
|
"z-index",
|
|
|
|
"box-sizing",
|
|
|
|
"display"
|
2013-08-12 14:43:19 +00:00
|
|
|
]) {
|
|
|
|
layout[prop] = style.getPropertyValue(prop);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.autoMargins) {
|
|
|
|
layout.autoMargins = this.processMargins(this.cssLogic);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i in this.map) {
|
|
|
|
let property = this.map[i].property;
|
2015-02-24 08:23:00 +00:00
|
|
|
this.map[i].value = parseFloat(style.getPropertyValue(property));
|
2013-08-12 14:43:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return layout;
|
|
|
|
}, {
|
|
|
|
request: {
|
|
|
|
node: Arg(0, "domnode"),
|
|
|
|
autoMargins: Option(1, "boolean")
|
|
|
|
},
|
|
|
|
response: RetVal("json")
|
|
|
|
}),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find 'auto' margin properties.
|
|
|
|
*/
|
|
|
|
processMargins: function(cssLogic) {
|
|
|
|
let margins = {};
|
|
|
|
|
|
|
|
for (let prop of ["top", "bottom", "left", "right"]) {
|
|
|
|
let info = cssLogic.getPropertyInfo("margin-" + prop);
|
|
|
|
let selectors = info.matchedSelectors;
|
|
|
|
if (selectors && selectors.length > 0 && selectors[0].value == "auto") {
|
|
|
|
margins[prop] = "auto";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return margins;
|
|
|
|
},
|
|
|
|
|
2014-07-07 07:18:00 +00:00
|
|
|
/**
|
|
|
|
* On page navigation, tidy up remaining objects.
|
|
|
|
*/
|
|
|
|
onFrameUnload: function() {
|
|
|
|
this._styleElement = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function to addNewRule to construct a new style tag in the document.
|
|
|
|
* @returns DOMElement of the style tag
|
|
|
|
*/
|
|
|
|
get styleElement() {
|
|
|
|
if (!this._styleElement) {
|
|
|
|
let document = this.inspector.window.document;
|
2015-06-17 09:50:28 +00:00
|
|
|
let style = document.createElementNS(XHTML_NS, "style");
|
2014-07-07 07:18:00 +00:00
|
|
|
style.setAttribute("type", "text/css");
|
2014-07-11 16:02:00 +00:00
|
|
|
document.documentElement.appendChild(style);
|
2014-07-07 07:18:00 +00:00
|
|
|
this._styleElement = style;
|
|
|
|
}
|
|
|
|
|
|
|
|
return this._styleElement;
|
|
|
|
},
|
|
|
|
|
2015-05-28 00:36:17 +00:00
|
|
|
/**
|
|
|
|
* Helper function for adding a new rule and getting its applied style
|
|
|
|
* properties
|
|
|
|
* @param NodeActor node
|
|
|
|
* @param CSSStyleRUle rule
|
|
|
|
* @returns Object containing its applied style properties
|
|
|
|
*/
|
|
|
|
getNewAppliedProps: function(node, rule) {
|
|
|
|
let ruleActor = this._styleRef(rule);
|
|
|
|
return this.getAppliedProps(node, [{ rule: ruleActor }],
|
|
|
|
{ matchedSelectors: true });
|
|
|
|
},
|
|
|
|
|
2014-07-07 07:18:00 +00:00
|
|
|
/**
|
|
|
|
* Adds a new rule, and returns the new StyleRuleActor.
|
2015-06-08 16:25:00 +00:00
|
|
|
* @param NodeActor node
|
|
|
|
* @param [string] pseudoClasses The list of pseudo classes to append to the
|
|
|
|
* new selector.
|
2014-07-07 07:18:00 +00:00
|
|
|
* @returns StyleRuleActor of the new rule
|
|
|
|
*/
|
2015-06-08 16:25:00 +00:00
|
|
|
addNewRule: method(function(node, pseudoClasses) {
|
2014-07-07 07:18:00 +00:00
|
|
|
let style = this.styleElement;
|
|
|
|
let sheet = style.sheet;
|
2015-06-01 15:38:00 +00:00
|
|
|
let cssRules = sheet.cssRules;
|
2014-07-07 07:18:00 +00:00
|
|
|
let rawNode = node.rawNode;
|
|
|
|
|
|
|
|
let selector;
|
|
|
|
if (rawNode.id) {
|
2015-06-01 15:38:00 +00:00
|
|
|
selector = "#" + CSS.escape(rawNode.id);
|
2014-07-07 07:18:00 +00:00
|
|
|
} else if (rawNode.className) {
|
2015-06-18 16:06:56 +00:00
|
|
|
selector = "." + [...rawNode.classList].map(c => CSS.escape(c)).join(".");
|
2014-07-07 07:18:00 +00:00
|
|
|
} else {
|
|
|
|
selector = rawNode.tagName.toLowerCase();
|
|
|
|
}
|
|
|
|
|
2015-06-08 16:25:00 +00:00
|
|
|
if (pseudoClasses && pseudoClasses.length > 0) {
|
|
|
|
selector += pseudoClasses.join("");
|
|
|
|
}
|
|
|
|
|
2015-06-01 15:38:00 +00:00
|
|
|
let index = sheet.insertRule(selector + " {}", cssRules.length);
|
|
|
|
return this.getNewAppliedProps(node, cssRules.item(index));
|
2014-07-07 07:18:00 +00:00
|
|
|
}, {
|
|
|
|
request: {
|
2015-06-08 16:25:00 +00:00
|
|
|
node: Arg(0, "domnode"),
|
|
|
|
pseudoClasses: Arg(1, "nullable:array:string")
|
2014-07-07 07:18:00 +00:00
|
|
|
},
|
|
|
|
response: RetVal("appliedStylesReturn")
|
|
|
|
}),
|
2013-07-23 22:51:58 +00:00
|
|
|
});
|
|
|
|
exports.PageStyleActor = PageStyleActor;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Front object for the PageStyleActor
|
|
|
|
*/
|
2015-06-17 09:50:28 +00:00
|
|
|
let PageStyleFront = protocol.FrontClass(PageStyleActor, {
|
2013-07-23 22:51:58 +00:00
|
|
|
initialize: function(conn, form, ctx, detail) {
|
|
|
|
protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
|
|
|
|
this.inspector = this.parent();
|
|
|
|
},
|
|
|
|
|
2014-12-10 08:09:19 +00:00
|
|
|
form: function(form, detail) {
|
|
|
|
if (detail === "actorid") {
|
|
|
|
this.actorID = form;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._form = form;
|
|
|
|
},
|
|
|
|
|
2013-07-23 22:51:58 +00:00
|
|
|
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"
|
|
|
|
}),
|
|
|
|
|
2014-12-10 08:09:19 +00:00
|
|
|
getApplied: protocol.custom(Task.async(function*(node, options={}) {
|
|
|
|
// If the getApplied method doesn't recreate the style cache itself, this
|
|
|
|
// means a call to cssLogic.highlight is required before trying to access
|
|
|
|
// the applied rules. Issue a request to getLayout if this is the case.
|
|
|
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1103993#c16.
|
|
|
|
if (!this._form.traits || !this._form.traits.getAppliedCreatesStyleCache) {
|
|
|
|
yield this.getLayout(node);
|
|
|
|
}
|
|
|
|
let ret = yield this._getApplied(node, options);
|
|
|
|
return ret.entries;
|
|
|
|
}), {
|
2013-07-23 22:51:58 +00:00
|
|
|
impl: "_getApplied"
|
2014-07-07 07:18:00 +00:00
|
|
|
}),
|
|
|
|
|
2015-06-08 16:25:00 +00:00
|
|
|
addNewRule: protocol.custom(function(node, pseudoClasses) {
|
|
|
|
return this._addNewRule(node, pseudoClasses).then(ret => {
|
2014-07-07 07:18:00 +00:00
|
|
|
return ret.entries[0];
|
|
|
|
});
|
|
|
|
}, {
|
|
|
|
impl: "_addNewRule"
|
2013-07-23 22:51:58 +00:00
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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).
|
|
|
|
*/
|
2015-06-17 09:50:28 +00:00
|
|
|
let StyleRuleActor = protocol.ActorClass({
|
2013-07-23 22:51:58 +00:00
|
|
|
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;
|
2014-07-20 03:08:43 +00:00
|
|
|
if ((this.rawRule instanceof Ci.nsIDOMCSSStyleRule ||
|
|
|
|
this.rawRule instanceof Ci.nsIDOMMozCSSKeyframeRule) &&
|
|
|
|
this.rawRule.parentStyleSheet) {
|
2013-07-23 22:51:58 +00:00
|
|
|
this.line = DOMUtils.getRuleLine(this.rawRule);
|
2013-12-07 07:52:32 +00:00
|
|
|
this.column = DOMUtils.getRuleColumn(this.rawRule);
|
2013-07-23 22:51:58 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Fake a rule
|
|
|
|
this.type = ELEMENT_STYLE;
|
|
|
|
this.rawNode = item;
|
|
|
|
this.rawRule = {
|
|
|
|
style: item.style,
|
2015-06-17 09:50:28 +00:00
|
|
|
toString: function() {
|
|
|
|
return "[element rule " + this.style + "]";
|
|
|
|
}
|
|
|
|
};
|
2013-07-23 22:51:58 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-05-28 15:54:00 +00:00
|
|
|
get conn() {
|
|
|
|
return this.pageStyle.conn;
|
|
|
|
},
|
2013-07-23 22:51:58 +00:00
|
|
|
|
|
|
|
// Objects returned by this actor are owned by the PageStyleActor
|
|
|
|
// to which this rule belongs.
|
2015-05-28 15:54:00 +00:00
|
|
|
get marshallPool() {
|
|
|
|
return this.pageStyle;
|
|
|
|
},
|
2013-07-23 22:51:58 +00:00
|
|
|
|
2014-06-24 08:02:16 +00:00
|
|
|
getDocument: function(sheet) {
|
|
|
|
let document;
|
|
|
|
|
|
|
|
if (sheet.ownerNode instanceof Ci.nsIDOMHTMLDocument) {
|
|
|
|
document = sheet.ownerNode;
|
|
|
|
} else {
|
|
|
|
document = sheet.ownerNode.ownerDocument;
|
|
|
|
}
|
|
|
|
|
|
|
|
return document;
|
|
|
|
},
|
|
|
|
|
2015-06-17 09:50:28 +00:00
|
|
|
toString: function() {
|
|
|
|
return "[StyleRuleActor for " + this.rawRule + "]"
|
|
|
|
},
|
2013-07-23 22:51:58 +00:00
|
|
|
|
|
|
|
form: function(detail) {
|
|
|
|
if (detail === "actorid") {
|
|
|
|
return this.actorID;
|
|
|
|
}
|
|
|
|
|
|
|
|
let form = {
|
|
|
|
actor: this.actorID,
|
|
|
|
type: this.type,
|
|
|
|
line: this.line || undefined,
|
2015-05-28 00:36:17 +00:00
|
|
|
column: this.column,
|
|
|
|
traits: {
|
|
|
|
// Whether the style rule actor implements the modifySelector2 method
|
|
|
|
// that allows for unmatched rule to be added
|
|
|
|
modifySelectorUnmatched: true,
|
|
|
|
}
|
2013-07-23 22:51:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if (this.rawRule.parentRule) {
|
|
|
|
form.parentRule = this.pageStyle._styleRef(this.rawRule.parentRule).actorID;
|
2015-03-23 17:33:02 +00:00
|
|
|
|
|
|
|
// CSS rules that we call media rules are STYLE_RULES that are children
|
|
|
|
// of MEDIA_RULEs. We need to check the parentRule to check if a rule is
|
|
|
|
// a media rule so we do this here instead of in the switch statement
|
|
|
|
// below.
|
|
|
|
if (this.rawRule.parentRule.type === Ci.nsIDOMCSSRule.MEDIA_RULE) {
|
|
|
|
form.media = [];
|
|
|
|
for (let i = 0, n = this.rawRule.parentRule.media.length; i < n; i++) {
|
|
|
|
form.media.push(this.rawRule.parentRule.media.item(i));
|
|
|
|
}
|
|
|
|
}
|
2013-07-23 22:51:58 +00:00
|
|
|
}
|
|
|
|
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.
|
2015-04-15 11:49:00 +00:00
|
|
|
let doc = this.rawNode.ownerDocument;
|
|
|
|
form.href = doc.location ? doc.location.href : "";
|
2013-07-23 22:51:58 +00:00
|
|
|
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;
|
2014-07-20 03:08:43 +00:00
|
|
|
case Ci.nsIDOMCSSRule.KEYFRAMES_RULE:
|
|
|
|
form.cssText = this.rawRule.cssText;
|
|
|
|
form.name = this.rawRule.name;
|
|
|
|
break;
|
|
|
|
case Ci.nsIDOMCSSRule.KEYFRAME_RULE:
|
|
|
|
form.cssText = this.rawStyle.cssText || "";
|
|
|
|
form.keyText = this.rawRule.keyText || "";
|
|
|
|
break;
|
2013-07-23 22:51:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2015-06-17 09:50:28 +00:00
|
|
|
// Use a fresh element for each call to this function to prevent side
|
|
|
|
// effects that pop up based on property values that were already set on the
|
|
|
|
// element.
|
2013-09-27 18:41:30 +00:00
|
|
|
|
|
|
|
let document;
|
|
|
|
if (this.rawNode) {
|
|
|
|
document = this.rawNode.ownerDocument;
|
|
|
|
} else {
|
2013-12-13 19:17:23 +00:00
|
|
|
let parentStyleSheet = this.rawRule.parentStyleSheet;
|
|
|
|
while (parentStyleSheet.ownerRule &&
|
|
|
|
parentStyleSheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) {
|
|
|
|
parentStyleSheet = parentStyleSheet.ownerRule.parentStyleSheet;
|
|
|
|
}
|
|
|
|
|
2014-06-24 08:02:16 +00:00
|
|
|
document = this.getDocument(parentStyleSheet);
|
2013-09-27 18:41:30 +00:00
|
|
|
}
|
|
|
|
|
2015-02-23 17:40:00 +00:00
|
|
|
let tempElement = document.createElementNS(XHTML_NS, "div");
|
2013-09-12 16:18:29 +00:00
|
|
|
|
2013-07-23 22:51:58 +00:00
|
|
|
for (let mod of modifications) {
|
|
|
|
if (mod.type === "set") {
|
2013-09-12 16:18:29 +00:00
|
|
|
tempElement.style.setProperty(mod.name, mod.value, mod.priority || "");
|
|
|
|
this.rawStyle.setProperty(mod.name,
|
|
|
|
tempElement.style.getPropertyValue(mod.name), mod.priority || "");
|
2013-07-23 22:51:58 +00:00
|
|
|
} else if (mod.type === "remove") {
|
|
|
|
this.rawStyle.removeProperty(mod.name);
|
|
|
|
}
|
|
|
|
}
|
2013-09-12 16:18:29 +00:00
|
|
|
|
2013-07-23 22:51:58 +00:00
|
|
|
return this;
|
|
|
|
}, {
|
|
|
|
request: { modifications: Arg(0, "array:json") },
|
|
|
|
response: { rule: RetVal("domstylerule") }
|
2014-06-24 08:02:16 +00:00
|
|
|
}),
|
|
|
|
|
|
|
|
/**
|
2015-05-28 00:36:17 +00:00
|
|
|
* Helper function for modifySelector and modifySelector2, inserts the new
|
|
|
|
* rule with the new selector into the parent style sheet and removes the
|
|
|
|
* current rule. Returns the newly inserted css rule or null if the rule is
|
|
|
|
* unsuccessfully inserted to the parent style sheet.
|
|
|
|
*
|
|
|
|
* @param string value
|
|
|
|
* The new selector value
|
|
|
|
*
|
|
|
|
* @returns CSSRule
|
|
|
|
* The new CSS rule added
|
|
|
|
*/
|
|
|
|
_addNewSelector: function(value) {
|
|
|
|
let rule = this.rawRule;
|
|
|
|
let parentStyleSheet = rule.parentStyleSheet;
|
|
|
|
let cssRules = parentStyleSheet.cssRules;
|
|
|
|
let cssText = rule.cssText;
|
|
|
|
let selectorText = rule.selectorText;
|
|
|
|
|
|
|
|
for (let i = 0; i < cssRules.length; i++) {
|
|
|
|
if (rule === cssRules.item(i)) {
|
|
|
|
try {
|
|
|
|
// Inserts the new style rule into the current style sheet and
|
|
|
|
// delete the current rule
|
|
|
|
let ruleText = cssText.slice(selectorText.length).trim();
|
|
|
|
parentStyleSheet.insertRule(value + " " + ruleText, i);
|
|
|
|
parentStyleSheet.deleteRule(i + 1);
|
|
|
|
return cssRules.item(i);
|
2015-06-17 09:50:28 +00:00
|
|
|
} catch(e) {
|
|
|
|
// The selector could be invalid, or the rule could fail to insert.
|
|
|
|
// If that happens, the method returns null.
|
|
|
|
}
|
2015-05-28 00:36:17 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Modify the current rule's selector by inserting a new rule with the new
|
|
|
|
* selector value and removing the current rule.
|
|
|
|
*
|
|
|
|
* Note this method was kept for backward compatibility, but unmatched rules
|
|
|
|
* support was added in FF41.
|
|
|
|
*
|
2014-06-24 08:02:16 +00:00
|
|
|
* @param string value
|
|
|
|
* The new selector value
|
|
|
|
* @returns boolean
|
|
|
|
* Returns a boolean if the selector in the stylesheet was modified,
|
|
|
|
* and false otherwise
|
|
|
|
*/
|
|
|
|
modifySelector: method(function(value) {
|
|
|
|
if (this.type === ELEMENT_STYLE) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-05-28 00:36:17 +00:00
|
|
|
let document = this.getDocument(this.rawRule.parentStyleSheet);
|
2014-06-24 08:02:16 +00:00
|
|
|
// Extract the selector, and pseudo elements and classes
|
|
|
|
let [selector, pseudoProp] = value.split(/(:{1,2}.+$)/);
|
|
|
|
let selectorElement;
|
|
|
|
|
|
|
|
try {
|
|
|
|
selectorElement = document.querySelector(selector);
|
|
|
|
} catch (e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the selector is valid and not the same as the original
|
|
|
|
// selector
|
2015-05-28 00:36:17 +00:00
|
|
|
if (selectorElement && this.rawRule.selectorText !== value) {
|
|
|
|
this._addNewSelector(value);
|
2014-06-24 08:02:16 +00:00
|
|
|
return true;
|
|
|
|
}
|
2015-06-17 09:50:28 +00:00
|
|
|
return false;
|
2014-06-24 08:02:16 +00:00
|
|
|
}, {
|
|
|
|
request: { selector: Arg(0, "string") },
|
|
|
|
response: { isModified: RetVal("boolean") },
|
|
|
|
}),
|
2015-05-28 00:36:17 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Modify the current rule's selector by inserting a new rule with the new
|
|
|
|
* selector value and removing the current rule.
|
|
|
|
*
|
|
|
|
* In contrast with the modifySelector method which was used before FF41,
|
|
|
|
* this method also returns information about the new rule and applied style
|
|
|
|
* so that consumers can immediately display the new rule, whether or not the
|
|
|
|
* selector matches the current element without having to refresh the whole
|
|
|
|
* list.
|
|
|
|
*
|
|
|
|
* @param DOMNode node
|
|
|
|
* The current selected element
|
|
|
|
* @param string value
|
|
|
|
* The new selector value
|
|
|
|
* @returns Object
|
|
|
|
* Returns an object that contains the applied style properties of the
|
|
|
|
* new rule and a boolean indicating whether or not the new selector
|
|
|
|
* matches the current selected element
|
|
|
|
*/
|
|
|
|
modifySelector2: method(function(node, value) {
|
|
|
|
let isMatching = false;
|
|
|
|
let ruleProps = null;
|
|
|
|
|
|
|
|
if (this.type === ELEMENT_STYLE ||
|
|
|
|
this.rawRule.selectorText === value) {
|
|
|
|
return { ruleProps, isMatching: true };
|
|
|
|
}
|
|
|
|
|
|
|
|
let newCssRule = this._addNewSelector(value);
|
|
|
|
if (newCssRule) {
|
|
|
|
ruleProps = this.pageStyle.getNewAppliedProps(node, newCssRule);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine if the new selector value matches the current selected element
|
|
|
|
try {
|
|
|
|
isMatching = node.rawNode.matches(value);
|
2015-06-17 09:50:28 +00:00
|
|
|
} catch(e) {
|
|
|
|
// This fails when value is an invalid selector.
|
|
|
|
}
|
2015-05-28 00:36:17 +00:00
|
|
|
|
|
|
|
return { ruleProps, isMatching };
|
|
|
|
}, {
|
|
|
|
request: {
|
|
|
|
node: Arg(0, "domnode"),
|
|
|
|
value: Arg(1, "string")
|
|
|
|
},
|
|
|
|
response: RetVal("modifiedStylesReturn")
|
|
|
|
})
|
2013-07-23 22:51:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Front for the StyleRule actor.
|
|
|
|
*/
|
2015-06-17 09:50:28 +00:00
|
|
|
let StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
|
2013-07-23 22:51:58 +00:00
|
|
|
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() {
|
2014-07-20 03:08:43 +00:00
|
|
|
return new RuleModificationList(this);
|
2013-07-23 22:51:58 +00:00
|
|
|
},
|
|
|
|
|
2015-05-28 15:54:00 +00:00
|
|
|
get type() {
|
|
|
|
return this._form.type;
|
|
|
|
},
|
|
|
|
get line() {
|
|
|
|
return this._form.line || -1;
|
|
|
|
},
|
|
|
|
get column() {
|
|
|
|
return this._form.column || -1;
|
|
|
|
},
|
2013-07-23 22:51:58 +00:00
|
|
|
get cssText() {
|
|
|
|
return this._form.cssText;
|
|
|
|
},
|
2014-07-20 03:08:43 +00:00
|
|
|
get keyText() {
|
|
|
|
return this._form.keyText;
|
|
|
|
},
|
|
|
|
get name() {
|
|
|
|
return this._form.name;
|
|
|
|
},
|
2013-07-23 22:51:58 +00:00
|
|
|
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;
|
2015-04-15 11:49:00 +00:00
|
|
|
return sheet ? sheet.href : "";
|
2013-12-16 19:49:17 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
get nodeHref() {
|
|
|
|
let sheet = this.parentStyleSheet;
|
|
|
|
return sheet ? sheet.nodeHref : "";
|
2013-07-23 22:51:58 +00:00
|
|
|
},
|
|
|
|
|
2015-05-28 00:36:17 +00:00
|
|
|
get supportsModifySelectorUnmatched() {
|
|
|
|
return this._form.traits && this._form.traits.modifySelectorUnmatched;
|
|
|
|
},
|
|
|
|
|
2015-06-17 09:50:28 +00:00
|
|
|
get location() {
|
2013-12-07 07:52:32 +00:00
|
|
|
return {
|
2014-08-22 19:30:00 +00:00
|
|
|
source: this.parentStyleSheet,
|
2013-12-07 07:52:32 +00:00
|
|
|
href: this.href,
|
|
|
|
line: this.line,
|
|
|
|
column: this.column
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2015-06-17 09:50:28 +00:00
|
|
|
getOriginalLocation: function() {
|
2013-12-07 07:52:32 +00:00
|
|
|
if (this._originalLocation) {
|
|
|
|
return promise.resolve(this._originalLocation);
|
|
|
|
}
|
|
|
|
let parentSheet = this.parentStyleSheet;
|
|
|
|
if (!parentSheet) {
|
2015-03-23 17:33:02 +00:00
|
|
|
// This rule doesn't belong to a stylesheet so it is an inline style.
|
|
|
|
// Inline styles do not have any mediaText so we can return early.
|
2013-12-07 07:52:32 +00:00
|
|
|
return promise.resolve(this.location);
|
|
|
|
}
|
|
|
|
return parentSheet.getOriginalLocation(this.line, this.column)
|
2014-08-22 19:30:00 +00:00
|
|
|
.then(({ fromSourceMap, source, line, column }) => {
|
2013-12-07 07:52:32 +00:00
|
|
|
let location = {
|
|
|
|
href: source,
|
|
|
|
line: line,
|
2015-03-23 17:33:02 +00:00
|
|
|
column: column,
|
|
|
|
mediaText: this.mediaText
|
|
|
|
};
|
2014-08-22 19:30:00 +00:00
|
|
|
if (fromSourceMap === false) {
|
|
|
|
location.source = this.parentStyleSheet;
|
|
|
|
}
|
2013-12-07 07:52:32 +00:00
|
|
|
if (!source) {
|
|
|
|
location.href = this.href;
|
|
|
|
}
|
|
|
|
this._originalLocation = location;
|
|
|
|
return location;
|
2015-03-23 17:33:02 +00:00
|
|
|
});
|
2015-05-28 00:36:17 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
modifySelector: protocol.custom(Task.async(function*(node, value) {
|
|
|
|
let response;
|
|
|
|
if (this.supportsModifySelectorUnmatched) {
|
|
|
|
// If the debugee supports adding unmatched rules (post FF41)
|
|
|
|
response = yield this.modifySelector2(node, value);
|
|
|
|
} else {
|
|
|
|
response = yield this._modifySelector(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (response.ruleProps) {
|
|
|
|
response.ruleProps = response.ruleProps.entries[0];
|
|
|
|
}
|
|
|
|
return response;
|
|
|
|
}), {
|
|
|
|
impl: "_modifySelector"
|
|
|
|
})
|
2013-07-23 22:51:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convenience API for building a list of attribute modifications
|
2015-08-19 20:58:40 +00:00
|
|
|
* for the `modifyProperties` request. A RuleModificationList holds a
|
|
|
|
* list of modifications that will be applied to a StyleRuleActor.
|
|
|
|
* The modifications are processed in the order in which they are
|
|
|
|
* added to the RuleModificationList.
|
2013-07-23 22:51:58 +00:00
|
|
|
*/
|
2015-06-17 09:50:28 +00:00
|
|
|
let RuleModificationList = Class({
|
2015-08-19 20:58:40 +00:00
|
|
|
/**
|
|
|
|
* Initialize a RuleModificationList.
|
|
|
|
* @param {StyleRuleFront} rule the associated rule
|
|
|
|
*/
|
2013-07-23 22:51:58 +00:00
|
|
|
initialize: function(rule) {
|
|
|
|
this.rule = rule;
|
|
|
|
this.modifications = [];
|
|
|
|
},
|
|
|
|
|
2015-08-19 20:58:40 +00:00
|
|
|
/**
|
|
|
|
* Apply the modifications in this object to the associated rule.
|
|
|
|
*
|
|
|
|
* @return {Promise} A promise which will be resolved when the modifications
|
|
|
|
* are complete; @see StyleRuleActor.modifyProperties.
|
|
|
|
*/
|
2013-07-23 22:51:58 +00:00
|
|
|
apply: function() {
|
|
|
|
return this.rule.modifyProperties(this.modifications);
|
|
|
|
},
|
2015-08-19 20:58:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a "set" entry to the modification list.
|
|
|
|
*
|
|
|
|
* @param {string} name the property's name
|
|
|
|
* @param {string} value the property's value
|
|
|
|
* @param {string} priority the property's priority, either the empty
|
|
|
|
* string or "important"
|
|
|
|
*/
|
2013-07-23 22:51:58 +00:00
|
|
|
setProperty: function(name, value, priority) {
|
|
|
|
this.modifications.push({
|
|
|
|
type: "set",
|
|
|
|
name: name,
|
|
|
|
value: value,
|
|
|
|
priority: priority
|
|
|
|
});
|
|
|
|
},
|
2015-08-19 20:58:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a "remove" entry to the modification list.
|
|
|
|
*
|
|
|
|
* @param {string} name the name of the property to remove
|
|
|
|
*/
|
2013-07-23 22:51:58 +00:00
|
|
|
removeProperty: function(name) {
|
|
|
|
this.modifications.push({
|
|
|
|
type: "remove",
|
|
|
|
name: name
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2014-11-25 15:36:44 +00:00
|
|
|
/**
|
|
|
|
* Helper function for getting an image preview of the given font.
|
|
|
|
*
|
|
|
|
* @param font {string}
|
|
|
|
* Name of font to preview
|
|
|
|
* @param doc {Document}
|
|
|
|
* Document to use to render font
|
|
|
|
* @param options {object}
|
|
|
|
* Object with options 'previewText' and 'previewFontSize'
|
|
|
|
*
|
|
|
|
* @return dataUrl
|
|
|
|
* The data URI of the font preview image
|
|
|
|
*/
|
|
|
|
function getFontPreviewData(font, doc, options) {
|
|
|
|
options = options || {};
|
|
|
|
let previewText = options.previewText || FONT_PREVIEW_TEXT;
|
|
|
|
let previewFontSize = options.previewFontSize || FONT_PREVIEW_FONT_SIZE;
|
|
|
|
let fillStyle = options.fillStyle || FONT_PREVIEW_FILLSTYLE;
|
|
|
|
let fontStyle = options.fontStyle || "";
|
|
|
|
|
|
|
|
let canvas = doc.createElementNS(XHTML_NS, "canvas");
|
|
|
|
let ctx = canvas.getContext("2d");
|
|
|
|
let fontValue = fontStyle + " " + previewFontSize + "px " + font + ", serif";
|
|
|
|
|
|
|
|
// Get the correct preview text measurements and set the canvas dimensions
|
|
|
|
ctx.font = fontValue;
|
|
|
|
ctx.fillStyle = fillStyle;
|
|
|
|
let textWidth = ctx.measureText(previewText).width;
|
2015-06-17 09:50:28 +00:00
|
|
|
|
|
|
|
canvas.width = textWidth * 2 + FONT_PREVIEW_OFFSET * 2;
|
2014-11-25 15:36:44 +00:00
|
|
|
canvas.height = previewFontSize * 3;
|
|
|
|
|
|
|
|
// we have to reset these after changing the canvas size
|
|
|
|
ctx.font = fontValue;
|
|
|
|
ctx.fillStyle = fillStyle;
|
|
|
|
|
|
|
|
// Oversample the canvas for better text quality
|
|
|
|
ctx.textBaseline = "top";
|
|
|
|
ctx.scale(2, 2);
|
2015-06-17 09:50:28 +00:00
|
|
|
ctx.fillText(previewText,
|
|
|
|
FONT_PREVIEW_OFFSET,
|
|
|
|
Math.round(previewFontSize / 3));
|
2014-11-25 15:36:44 +00:00
|
|
|
|
|
|
|
let dataURL = canvas.toDataURL("image/png");
|
|
|
|
|
|
|
|
return {
|
|
|
|
dataURL: dataURL,
|
2015-06-17 09:50:28 +00:00
|
|
|
size: textWidth + FONT_PREVIEW_OFFSET * 2
|
2014-11-25 15:36:44 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.getFontPreviewData = getFontPreviewData;
|
2015-08-24 14:36:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the text content of a rule given some CSS text, a line and a column
|
|
|
|
* Consider the following example:
|
|
|
|
* body {
|
|
|
|
* color: red;
|
|
|
|
* }
|
|
|
|
* p {
|
|
|
|
* line-height: 2em;
|
|
|
|
* color: blue;
|
|
|
|
* }
|
|
|
|
* Calling the function with the whole text above and line=4 and column=1 would
|
|
|
|
* return "line-height: 2em; color: blue;"
|
|
|
|
* @param {String} initialText
|
|
|
|
* @param {Number} line (1-indexed)
|
|
|
|
* @param {Number} column (1-indexed)
|
|
|
|
* @return {object} An object of the form {offset: number, text: string}
|
|
|
|
* The offset is the index into the input string where
|
|
|
|
* the rule text started. The text is the content of
|
|
|
|
* the rule.
|
|
|
|
*/
|
|
|
|
function getRuleText(initialText, line, column) {
|
|
|
|
if (typeof line === "undefined" || typeof column === "undefined") {
|
|
|
|
throw new Error("Location information is missing");
|
|
|
|
}
|
|
|
|
|
|
|
|
let {offset: textOffset, text} =
|
|
|
|
getTextAtLineColumn(initialText, line, column);
|
|
|
|
let lexer = DOMUtils.getCSSLexer(text);
|
|
|
|
|
|
|
|
// Search forward for the opening brace.
|
|
|
|
while (true) {
|
|
|
|
let token = lexer.nextToken();
|
|
|
|
if (!token) {
|
|
|
|
throw new Error("couldn't find start of the rule");
|
|
|
|
}
|
|
|
|
if (token.tokenType === "symbol" && token.text === "{") {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now collect text until we see the matching close brace.
|
|
|
|
let braceDepth = 1;
|
|
|
|
let startOffset, endOffset;
|
|
|
|
while (true) {
|
|
|
|
let token = lexer.nextToken();
|
|
|
|
if (!token) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (startOffset === undefined) {
|
|
|
|
startOffset = token.startOffset;
|
|
|
|
}
|
|
|
|
if (token.tokenType === "symbol") {
|
|
|
|
if (token.text === "{") {
|
|
|
|
++braceDepth;
|
|
|
|
} else if (token.text === "}") {
|
|
|
|
--braceDepth;
|
|
|
|
if (braceDepth == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
endOffset = token.endOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the rule was of the form "selector {" with no closing brace
|
|
|
|
// and no properties, just return an empty string.
|
|
|
|
if (startOffset === undefined) {
|
|
|
|
return {offset: 0, text: ""};
|
|
|
|
}
|
|
|
|
// Note that this approach will preserve comments, despite the fact
|
|
|
|
// that cssTokenizer skips them.
|
|
|
|
return {offset: textOffset + startOffset,
|
|
|
|
text: text.substring(startOffset, endOffset)};
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.getRuleText = getRuleText;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the offset and substring of |text| that starts at the given
|
|
|
|
* line and column.
|
|
|
|
* @param {String} text
|
|
|
|
* @param {Number} line (1-indexed)
|
|
|
|
* @param {Number} column (1-indexed)
|
|
|
|
* @return {object} An object of the form {offset: number, text: string},
|
|
|
|
* where the offset is the offset into the input string
|
|
|
|
* where the text starts, and where text is the text.
|
|
|
|
*/
|
|
|
|
function getTextAtLineColumn(text, line, column) {
|
|
|
|
let offset;
|
|
|
|
if (line > 1) {
|
|
|
|
let rx = new RegExp("(?:.*(?:\\r\\n|\\n|\\r|\\f)){" + (line - 1) + "}");
|
|
|
|
offset = rx.exec(text)[0].length;
|
|
|
|
} else {
|
|
|
|
offset = 0;
|
|
|
|
}
|
|
|
|
offset += column - 1;
|
|
|
|
return {offset: offset, text: text.substr(offset) };
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.getTextAtLineColumn = getTextAtLineColumn;
|