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