styles.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. require("devtools/shared/fronts/stylesheets");
  6. const {
  7. Front,
  8. FrontClassWithSpec,
  9. custom,
  10. preEvent
  11. } = require("devtools/shared/protocol");
  12. const {
  13. pageStyleSpec,
  14. styleRuleSpec
  15. } = require("devtools/shared/specs/styles");
  16. const promise = require("promise");
  17. const { Task } = require("devtools/shared/task");
  18. const { Class } = require("sdk/core/heritage");
  19. const { RuleRewriter } = require("devtools/shared/css/parsing-utils");
  20. /**
  21. * PageStyleFront, the front object for the PageStyleActor
  22. */
  23. const PageStyleFront = FrontClassWithSpec(pageStyleSpec, {
  24. initialize: function (conn, form, ctx, detail) {
  25. Front.prototype.initialize.call(this, conn, form, ctx, detail);
  26. this.inspector = this.parent();
  27. },
  28. form: function (form, detail) {
  29. if (detail === "actorid") {
  30. this.actorID = form;
  31. return;
  32. }
  33. this._form = form;
  34. },
  35. destroy: function () {
  36. Front.prototype.destroy.call(this);
  37. },
  38. get walker() {
  39. return this.inspector.walker;
  40. },
  41. get supportsAuthoredStyles() {
  42. return this._form.traits && this._form.traits.authoredStyles;
  43. },
  44. getMatchedSelectors: custom(function (node, property, options) {
  45. return this._getMatchedSelectors(node, property, options).then(ret => {
  46. return ret.matched;
  47. });
  48. }, {
  49. impl: "_getMatchedSelectors"
  50. }),
  51. getApplied: custom(Task.async(function* (node, options = {}) {
  52. // If the getApplied method doesn't recreate the style cache itself, this
  53. // means a call to cssLogic.highlight is required before trying to access
  54. // the applied rules. Issue a request to getLayout if this is the case.
  55. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1103993#c16.
  56. if (!this._form.traits || !this._form.traits.getAppliedCreatesStyleCache) {
  57. yield this.getLayout(node);
  58. }
  59. let ret = yield this._getApplied(node, options);
  60. return ret.entries;
  61. }), {
  62. impl: "_getApplied"
  63. }),
  64. addNewRule: custom(function (node, pseudoClasses) {
  65. let addPromise;
  66. if (this.supportsAuthoredStyles) {
  67. addPromise = this._addNewRule(node, pseudoClasses, true);
  68. } else {
  69. addPromise = this._addNewRule(node, pseudoClasses);
  70. }
  71. return addPromise.then(ret => {
  72. return ret.entries[0];
  73. });
  74. }, {
  75. impl: "_addNewRule"
  76. })
  77. });
  78. exports.PageStyleFront = PageStyleFront;
  79. /**
  80. * StyleRuleFront, the front for the StyleRule actor.
  81. */
  82. const StyleRuleFront = FrontClassWithSpec(styleRuleSpec, {
  83. initialize: function (client, form, ctx, detail) {
  84. Front.prototype.initialize.call(this, client, form, ctx, detail);
  85. },
  86. destroy: function () {
  87. Front.prototype.destroy.call(this);
  88. },
  89. form: function (form, detail) {
  90. if (detail === "actorid") {
  91. this.actorID = form;
  92. return;
  93. }
  94. this.actorID = form.actor;
  95. this._form = form;
  96. if (this._mediaText) {
  97. this._mediaText = null;
  98. }
  99. },
  100. /**
  101. * Ensure _form is updated when location-changed is emitted.
  102. */
  103. _locationChangedPre: preEvent("location-changed", function (line, column) {
  104. this._clearOriginalLocation();
  105. this._form.line = line;
  106. this._form.column = column;
  107. }),
  108. /**
  109. * Return a new RuleModificationList or RuleRewriter for this node.
  110. * A RuleRewriter will be returned when the rule's canSetRuleText
  111. * trait is true; otherwise a RuleModificationList will be
  112. * returned.
  113. *
  114. * @param {CssPropertiesFront} cssProperties
  115. * This is needed by the RuleRewriter.
  116. * @return {RuleModificationList}
  117. */
  118. startModifyingProperties: function (cssProperties) {
  119. if (this.canSetRuleText) {
  120. return new RuleRewriter(cssProperties.isKnown, this, this.authoredText);
  121. }
  122. return new RuleModificationList(this);
  123. },
  124. get type() {
  125. return this._form.type;
  126. },
  127. get line() {
  128. return this._form.line || -1;
  129. },
  130. get column() {
  131. return this._form.column || -1;
  132. },
  133. get cssText() {
  134. return this._form.cssText;
  135. },
  136. get authoredText() {
  137. return this._form.authoredText || this._form.cssText;
  138. },
  139. get declarations() {
  140. return this._form.declarations || [];
  141. },
  142. get keyText() {
  143. return this._form.keyText;
  144. },
  145. get name() {
  146. return this._form.name;
  147. },
  148. get selectors() {
  149. return this._form.selectors;
  150. },
  151. get media() {
  152. return this._form.media;
  153. },
  154. get mediaText() {
  155. if (!this._form.media) {
  156. return null;
  157. }
  158. if (this._mediaText) {
  159. return this._mediaText;
  160. }
  161. this._mediaText = this.media.join(", ");
  162. return this._mediaText;
  163. },
  164. get parentRule() {
  165. return this.conn.getActor(this._form.parentRule);
  166. },
  167. get parentStyleSheet() {
  168. return this.conn.getActor(this._form.parentStyleSheet);
  169. },
  170. get element() {
  171. return this.conn.getActor(this._form.element);
  172. },
  173. get href() {
  174. if (this._form.href) {
  175. return this._form.href;
  176. }
  177. let sheet = this.parentStyleSheet;
  178. return sheet ? sheet.href : "";
  179. },
  180. get nodeHref() {
  181. let sheet = this.parentStyleSheet;
  182. return sheet ? sheet.nodeHref : "";
  183. },
  184. get supportsModifySelectorUnmatched() {
  185. return this._form.traits && this._form.traits.modifySelectorUnmatched;
  186. },
  187. get canSetRuleText() {
  188. return this._form.traits && this._form.traits.canSetRuleText;
  189. },
  190. get location() {
  191. return {
  192. source: this.parentStyleSheet,
  193. href: this.href,
  194. line: this.line,
  195. column: this.column
  196. };
  197. },
  198. _clearOriginalLocation: function () {
  199. this._originalLocation = null;
  200. },
  201. getOriginalLocation: function () {
  202. if (this._originalLocation) {
  203. return promise.resolve(this._originalLocation);
  204. }
  205. let parentSheet = this.parentStyleSheet;
  206. if (!parentSheet) {
  207. // This rule doesn't belong to a stylesheet so it is an inline style.
  208. // Inline styles do not have any mediaText so we can return early.
  209. return promise.resolve(this.location);
  210. }
  211. return parentSheet.getOriginalLocation(this.line, this.column)
  212. .then(({ fromSourceMap, source, line, column }) => {
  213. let location = {
  214. href: source,
  215. line: line,
  216. column: column,
  217. mediaText: this.mediaText
  218. };
  219. if (fromSourceMap === false) {
  220. location.source = this.parentStyleSheet;
  221. }
  222. if (!source) {
  223. location.href = this.href;
  224. }
  225. this._originalLocation = location;
  226. return location;
  227. });
  228. },
  229. modifySelector: custom(Task.async(function* (node, value) {
  230. let response;
  231. if (this.supportsModifySelectorUnmatched) {
  232. // If the debugee supports adding unmatched rules (post FF41)
  233. if (this.canSetRuleText) {
  234. response = yield this.modifySelector2(node, value, true);
  235. } else {
  236. response = yield this.modifySelector2(node, value);
  237. }
  238. } else {
  239. response = yield this._modifySelector(value);
  240. }
  241. if (response.ruleProps) {
  242. response.ruleProps = response.ruleProps.entries[0];
  243. }
  244. return response;
  245. }), {
  246. impl: "_modifySelector"
  247. }),
  248. setRuleText: custom(function (newText) {
  249. this._form.authoredText = newText;
  250. return this._setRuleText(newText);
  251. }, {
  252. impl: "_setRuleText"
  253. })
  254. });
  255. exports.StyleRuleFront = StyleRuleFront;
  256. /**
  257. * Convenience API for building a list of attribute modifications
  258. * for the `modifyProperties` request. A RuleModificationList holds a
  259. * list of modifications that will be applied to a StyleRuleActor.
  260. * The modifications are processed in the order in which they are
  261. * added to the RuleModificationList.
  262. *
  263. * Objects of this type expose the same API as @see RuleRewriter.
  264. * This lets the inspector use (mostly) the same code, regardless of
  265. * whether the server implements setRuleText.
  266. */
  267. var RuleModificationList = Class({
  268. /**
  269. * Initialize a RuleModificationList.
  270. * @param {StyleRuleFront} rule the associated rule
  271. */
  272. initialize: function (rule) {
  273. this.rule = rule;
  274. this.modifications = [];
  275. },
  276. /**
  277. * Apply the modifications in this object to the associated rule.
  278. *
  279. * @return {Promise} A promise which will be resolved when the modifications
  280. * are complete; @see StyleRuleActor.modifyProperties.
  281. */
  282. apply: function () {
  283. return this.rule.modifyProperties(this.modifications);
  284. },
  285. /**
  286. * Add a "set" entry to the modification list.
  287. *
  288. * @param {Number} index index of the property in the rule.
  289. * This can be -1 in the case where
  290. * the rule does not support setRuleText;
  291. * generally for setting properties
  292. * on an element's style.
  293. * @param {String} name the property's name
  294. * @param {String} value the property's value
  295. * @param {String} priority the property's priority, either the empty
  296. * string or "important"
  297. */
  298. setProperty: function (index, name, value, priority) {
  299. this.modifications.push({
  300. type: "set",
  301. name: name,
  302. value: value,
  303. priority: priority
  304. });
  305. },
  306. /**
  307. * Add a "remove" entry to the modification list.
  308. *
  309. * @param {Number} index index of the property in the rule.
  310. * This can be -1 in the case where
  311. * the rule does not support setRuleText;
  312. * generally for setting properties
  313. * on an element's style.
  314. * @param {String} name the name of the property to remove
  315. */
  316. removeProperty: function (index, name) {
  317. this.modifications.push({
  318. type: "remove",
  319. name: name
  320. });
  321. },
  322. /**
  323. * Rename a property. This implementation acts like
  324. * |removeProperty|, because |setRuleText| is not available.
  325. *
  326. * @param {Number} index index of the property in the rule.
  327. * This can be -1 in the case where
  328. * the rule does not support setRuleText;
  329. * generally for setting properties
  330. * on an element's style.
  331. * @param {String} name current name of the property
  332. *
  333. * This parameter is also passed, but as it is not used in this
  334. * implementation, it is omitted. It is documented here as this
  335. * code also defined the interface implemented by @see RuleRewriter.
  336. * @param {String} newName new name of the property
  337. */
  338. renameProperty: function (index, name) {
  339. this.removeProperty(index, name);
  340. },
  341. /**
  342. * Enable or disable a property. This implementation acts like
  343. * |removeProperty| when disabling, or a no-op when enabling,
  344. * because |setRuleText| is not available.
  345. *
  346. * @param {Number} index index of the property in the rule.
  347. * This can be -1 in the case where
  348. * the rule does not support setRuleText;
  349. * generally for setting properties
  350. * on an element's style.
  351. * @param {String} name current name of the property
  352. * @param {Boolean} isEnabled true if the property should be enabled;
  353. * false if it should be disabled
  354. */
  355. setPropertyEnabled: function (index, name, isEnabled) {
  356. if (!isEnabled) {
  357. this.removeProperty(index, name);
  358. }
  359. },
  360. /**
  361. * Create a new property. This implementation does nothing, because
  362. * |setRuleText| is not available.
  363. *
  364. * These parameters are passed, but as they are not used in this
  365. * implementation, they are omitted. They are documented here as
  366. * this code also defined the interface implemented by @see
  367. * RuleRewriter.
  368. *
  369. * @param {Number} index index of the property in the rule.
  370. * This can be -1 in the case where
  371. * the rule does not support setRuleText;
  372. * generally for setting properties
  373. * on an element's style.
  374. * @param {String} name name of the new property
  375. * @param {String} value value of the new property
  376. * @param {String} priority priority of the new property; either
  377. * the empty string or "important"
  378. * @param {Boolean} enabled True if the new property should be
  379. * enabled, false if disabled
  380. */
  381. createProperty: function () {
  382. // Nothing.
  383. },
  384. });