chrome-devtools-frontend
Version:
Chrome DevTools UI
343 lines (304 loc) • 8.77 kB
JavaScript
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as TextUtils from '../text_utils/text_utils.js';
import {CSSMedia} from './CSSMedia.js';
import {CSSModel, Edit} from './CSSModel.js'; // eslint-disable-line no-unused-vars
import {CSSStyleDeclaration, Type} from './CSSStyleDeclaration.js';
import {CSSStyleSheetHeader} from './CSSStyleSheetHeader.js'; // eslint-disable-line no-unused-vars
export class CSSRule {
/**
* @param {!CSSModel} cssModel
* @param {{style: !Protocol.CSS.CSSStyle, styleSheetId: (string|undefined), origin: !Protocol.CSS.StyleSheetOrigin}} payload
*/
constructor(cssModel, payload) {
this._cssModel = cssModel;
this.styleSheetId = payload.styleSheetId;
if (this.styleSheetId) {
const styleSheetHeader = this._getStyleSheetHeader(this.styleSheetId);
this.sourceURL = styleSheetHeader.sourceURL;
}
this.origin = payload.origin;
this.style = new CSSStyleDeclaration(this._cssModel, this, payload.style, Type.Regular);
}
/**
* @param {!Edit} edit
*/
rebase(edit) {
if (this.styleSheetId !== edit.styleSheetId) {
return;
}
this.style.rebase(edit);
}
/**
* @return {string}
*/
resourceURL() {
if (!this.styleSheetId) {
return '';
}
const styleSheetHeader = this._getStyleSheetHeader(this.styleSheetId);
return styleSheetHeader.resourceURL();
}
/**
* @return {boolean}
*/
isUserAgent() {
return this.origin === Protocol.CSS.StyleSheetOrigin.UserAgent;
}
/**
* @return {boolean}
*/
isInjected() {
return this.origin === Protocol.CSS.StyleSheetOrigin.Injected;
}
/**
* @return {boolean}
*/
isViaInspector() {
return this.origin === Protocol.CSS.StyleSheetOrigin.Inspector;
}
/**
* @return {boolean}
*/
isRegular() {
return this.origin === Protocol.CSS.StyleSheetOrigin.Regular;
}
/**
* @return {!CSSModel}
*/
cssModel() {
return this._cssModel;
}
/**
* @param {string} styleSheetId
* @return {!CSSStyleSheetHeader}
*/
_getStyleSheetHeader(styleSheetId) {
const styleSheetHeader = this._cssModel.styleSheetHeaderForId(styleSheetId);
console.assert(styleSheetHeader !== null);
return /** @type {!CSSStyleSheetHeader} */ (styleSheetHeader);
}
}
class CSSValue {
/**
* @param {!Protocol.CSS.Value} payload
*/
constructor(payload) {
this.text = payload.text;
if (payload.range) {
this.range = TextUtils.TextRange.TextRange.fromObject(payload.range);
}
}
/**
* @param {!Edit} edit
*/
rebase(edit) {
if (!this.range) {
return;
}
this.range = this.range.rebaseAfterTextEdit(edit.oldRange, edit.newRange);
}
}
export class CSSStyleRule extends CSSRule {
/**
* @param {!CSSModel} cssModel
* @param {!Protocol.CSS.CSSRule} payload
* @param {boolean=} wasUsed
*/
constructor(cssModel, payload, wasUsed) {
// TODO(crbug.com/1011811): Replace with spread operator or better types once Closure is gone.
super(cssModel, {origin: payload.origin, style: payload.style, styleSheetId: payload.styleSheetId});
/** @type {!Array.<!CSSValue>} */
this.selectors;
this._reinitializeSelectors(payload.selectorList);
this.media = payload.media ? CSSMedia.parseMediaArrayPayload(cssModel, payload.media) : [];
this.wasUsed = wasUsed || false;
}
/**
* @param {!CSSModel} cssModel
* @param {string} selectorText
* @return {!CSSStyleRule}
*/
static createDummyRule(cssModel, selectorText) {
const dummyPayload = {
selectorList: {
text: '',
selectors: [{text: selectorText, value: undefined}],
},
style: {
styleSheetId: '0',
range: new TextUtils.TextRange.TextRange(0, 0, 0, 0),
shorthandEntries: [],
cssProperties: []
},
origin: Protocol.CSS.StyleSheetOrigin.Inspector,
};
return new CSSStyleRule(cssModel, /** @type {!Protocol.CSS.CSSRule} */ (dummyPayload));
}
/**
* @param {!Protocol.CSS.SelectorList} selectorList
*/
_reinitializeSelectors(selectorList) {
this.selectors = [];
for (let i = 0; i < selectorList.selectors.length; ++i) {
this.selectors.push(new CSSValue(selectorList.selectors[i]));
}
}
/**
* @param {string} newSelector
* @return {!Promise.<boolean>}
*/
setSelectorText(newSelector) {
const styleSheetId = this.styleSheetId;
if (!styleSheetId) {
throw 'No rule stylesheet id';
}
const range = this.selectorRange();
if (!range) {
throw 'Rule selector is not editable';
}
return this._cssModel.setSelectorText(styleSheetId, range, newSelector);
}
/**
* @return {string}
*/
selectorText() {
return this.selectors.map(selector => selector.text).join(', ');
}
/**
* @return {?TextUtils.TextRange.TextRange}
*/
selectorRange() {
const firstRange = this.selectors[0].range;
const lastRange = this.selectors[this.selectors.length - 1].range;
if (!firstRange || !lastRange) {
return null;
}
return new TextUtils.TextRange.TextRange(
firstRange.startLine, firstRange.startColumn, lastRange.endLine, lastRange.endColumn);
}
/**
* @param {number} selectorIndex
* @return {number}
*/
lineNumberInSource(selectorIndex) {
const selector = this.selectors[selectorIndex];
if (!selector || !selector.range || !this.styleSheetId) {
return 0;
}
const styleSheetHeader = this._getStyleSheetHeader(this.styleSheetId);
return styleSheetHeader.lineNumberInSource(selector.range.startLine);
}
/**
* @param {number} selectorIndex
* @return {number|undefined}
*/
columnNumberInSource(selectorIndex) {
const selector = this.selectors[selectorIndex];
if (!selector || !selector.range || !this.styleSheetId) {
return undefined;
}
const styleSheetHeader = this._getStyleSheetHeader(this.styleSheetId);
return styleSheetHeader.columnNumberInSource(selector.range.startLine, selector.range.startColumn);
}
/**
* @override
* @param {!Edit} edit
*/
rebase(edit) {
if (this.styleSheetId !== edit.styleSheetId) {
return;
}
const range = this.selectorRange();
if (range && range.equal(edit.oldRange)) {
this._reinitializeSelectors(/** @type {!Protocol.CSS.SelectorList} */ (edit.payload));
} else {
for (let i = 0; i < this.selectors.length; ++i) {
this.selectors[i].rebase(edit);
}
}
for (const media of this.media) {
media.rebase(edit);
}
super.rebase(edit);
}
}
export class CSSKeyframesRule {
/**
* @param {!CSSModel} cssModel
* @param {!Protocol.CSS.CSSKeyframesRule} payload
*/
constructor(cssModel, payload) {
this._cssModel = cssModel;
this._animationName = new CSSValue(payload.animationName);
this._keyframes = payload.keyframes.map(keyframeRule => new CSSKeyframeRule(cssModel, keyframeRule));
}
/**
* @return {!CSSValue}
*/
name() {
return this._animationName;
}
/**
* @return {!Array.<!CSSKeyframeRule>}
*/
keyframes() {
return this._keyframes;
}
}
export class CSSKeyframeRule extends CSSRule {
/**
* @param {!CSSModel} cssModel
* @param {!Protocol.CSS.CSSKeyframeRule} payload
*/
constructor(cssModel, payload) {
// TODO(crbug.com/1011811): Replace with spread operator or better types once Closure is gone.
super(cssModel, {origin: payload.origin, style: payload.style, styleSheetId: payload.styleSheetId});
/** @type {!CSSValue} */
this._keyText;
this._reinitializeKey(payload.keyText);
}
/**
* @return {!CSSValue}
*/
key() {
return this._keyText;
}
/**
* @param {!Protocol.CSS.Value} payload
*/
_reinitializeKey(payload) {
this._keyText = new CSSValue(payload);
}
/**
* @override
* @param {!Edit} edit
*/
rebase(edit) {
if (this.styleSheetId !== edit.styleSheetId || !this._keyText.range) {
return;
}
if (edit.oldRange.equal(this._keyText.range)) {
this._reinitializeKey(/** @type {!Protocol.CSS.Value} */ (edit.payload));
} else {
this._keyText.rebase(edit);
}
super.rebase(edit);
}
/**
* @param {string} newKeyText
* @return {!Promise.<boolean>}
*/
setKeyText(newKeyText) {
const styleSheetId = this.styleSheetId;
if (!styleSheetId) {
throw 'No rule stylesheet id';
}
const range = this._keyText.range;
if (!range) {
throw 'Keyframe key is not editable';
}
return this._cssModel.setKeyframeKey(styleSheetId, range, newKeyText);
}
}