Bug 1664941: Connect style actor and stylesheet watcher. r=jdescottes

Differential Revision: https://phabricator.services.mozilla.com/D90651
This commit is contained in:
Daisuke Akatsuka 2020-10-29 06:26:20 +00:00
Родитель 72da707476
Коммит 473676fda0
12 изменённых файлов: 462 добавлений и 105 удалений

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

@ -21,6 +21,13 @@ const {
unescapeCSSComment, unescapeCSSComment,
} = require("devtools/shared/css/parsing-utils"); } = require("devtools/shared/css/parsing-utils");
loader.lazyRequireGetter(
this,
["getIndentationFromPrefs", "getIndentationFromString"],
"devtools/shared/indentation",
true
);
// Used to test whether a newline appears anywhere in some text. // Used to test whether a newline appears anywhere in some text.
const NEWLINE_RX = /[\r\n]/; const NEWLINE_RX = /[\r\n]/;
// Used to test whether a bit of text starts an empty comment, either // Used to test whether a bit of text starts an empty comment, either
@ -474,7 +481,28 @@ RuleRewriter.prototype = {
* that holds the default indentation that should be used * that holds the default indentation that should be used
* for edits to the rule. * for edits to the rule.
*/ */
getDefaultIndentation: function() { getDefaultIndentation: async function() {
if (!this.rule.parentStyleSheet) {
return null;
}
if (this.rule.parentStyleSheet.resourceId) {
const prefIndent = getIndentationFromPrefs();
if (prefIndent) {
const { indentUnit, indentWithTabs } = prefIndent;
return indentWithTabs ? "\t" : " ".repeat(indentUnit);
}
const styleSheetsFront = await this.rule.targetFront.getFront(
"stylesheets"
);
const { str: source } = await styleSheetsFront.getText(
this.rule.parentStyleSheet.resourceId
);
const { indentUnit, indentWithTabs } = getIndentationFromString(source);
return indentWithTabs ? "\t" : " ".repeat(indentUnit);
}
return this.rule.parentStyleSheet.guessIndentation(); return this.rule.parentStyleSheet.guessIndentation();
}, },

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

@ -223,6 +223,14 @@ class StyleRuleFront extends FrontClassWithSpec(styleRuleSpec) {
} }
get parentStyleSheet() { get parentStyleSheet() {
const resourceWatcher = this.parentFront.resourceWatcher;
if (resourceWatcher) {
return resourceWatcher.getResourceById(
resourceWatcher.TYPES.STYLESHEET,
this._form.parentStyleSheet
);
}
return this.conn.getFrontByID(this._form.parentStyleSheet); return this.conn.getFrontByID(this._form.parentStyleSheet);
} }

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

@ -1391,7 +1391,8 @@ function SelectorView(tree, selectorInfo) {
}; };
this.sourceMapURLService = this.tree.inspector.toolbox.sourceMapURLService; this.sourceMapURLService = this.tree.inspector.toolbox.sourceMapURLService;
this._unsubscribeCallback = this.sourceMapURLService.subscribeByID( this._unsubscribeCallback = this.sourceMapURLService.subscribeByID(
this.generatedLocation.sheet.actorID, this.generatedLocation.sheet.resourceId ||
this.generatedLocation.sheet.actorID,
this.generatedLocation.line, this.generatedLocation.line,
this.generatedLocation.column, this.generatedLocation.column,
this._updateLocation this._updateLocation

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

@ -205,6 +205,21 @@ Inspector.prototype = {
// available for the top-level target. // available for the top-level target.
this._onFirstMarkupLoaded = this.once("markuploaded"); this._onFirstMarkupLoaded = this.once("markuploaded");
// If the server-side stylesheet watcher is enabled, we should start to watch
// stylesheet resources before instanciating the inspector front since pageStyle
// actor should refer the watcher.
if (
this.toolbox.resourceWatcher.hasWatcherSupport(
this.toolbox.resourceWatcher.TYPES.STYLESHEET
)
) {
this._isServerSideStyleSheetWatcherEnabled = true;
await this.toolbox.resourceWatcher.watchResources(
[this.toolbox.resourceWatcher.TYPES.STYLESHEET],
{ onAvailable: this.onResourceAvailable }
);
}
await this.toolbox.targetList.watchTargets( await this.toolbox.targetList.watchTargets(
[this.toolbox.targetList.TYPES.FRAME], [this.toolbox.targetList.TYPES.FRAME],
this._onTargetAvailable, this._onTargetAvailable,
@ -258,6 +273,11 @@ Inspector.prototype = {
async initInspectorFront(targetFront) { async initInspectorFront(targetFront) {
this.inspectorFront = await targetFront.getFront("inspector"); this.inspectorFront = await targetFront.getFront("inspector");
this.walker = this.inspectorFront.walker; this.walker = this.inspectorFront.walker;
// PageStyle front need the resource watcher when the server-side stylesheet watcher is enabled.
if (this._isServerSideStyleSheetWatcherEnabled) {
this.inspectorFront.pageStyle.resourceWatcher = this.toolbox.resourceWatcher;
}
}, },
get toolbox() { get toolbox() {

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

@ -348,7 +348,7 @@ RuleEditor.prototype = {
this._unsubscribeSourceMap(); this._unsubscribeSourceMap();
} }
this._unsubscribeSourceMap = this.sourceMapURLService.subscribeByID( this._unsubscribeSourceMap = this.sourceMapURLService.subscribeByID(
this.rule.sheet.actorID, this.rule.sheet.resourceId || this.rule.sheet.actorID,
this.rule.ruleLine, this.rule.ruleLine,
this.rule.ruleColumn, this.rule.ruleColumn,
this._updateLocation this._updateLocation

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

@ -37,7 +37,7 @@ exports.viewSourceInStyleEditor = async function(
const originalLocation = stylesheetFront const originalLocation = stylesheetFront
? await getOriginalLocation( ? await getOriginalLocation(
toolbox, toolbox,
stylesheetFront.actorID, stylesheetFront.resourceId,
generatedLine, generatedLine,
generatedColumn generatedColumn
) )

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

@ -14,7 +14,8 @@ add_task(async function() {
// below because the test-page is loaded with chrome:// URL and, right now, // below because the test-page is loaded with chrome:// URL and, right now,
// that means UA stylesheets are shown. So we avoid hardcoding the number of // that means UA stylesheets are shown. So we avoid hardcoding the number of
// stylesheets here. // stylesheets here.
ok(ui.editors.length, "The UI contains style sheets."); await waitUntil(() => ui.editors.length);
ok(true, "The UI contains style sheets.");
const rootEl = panel.panelWindow.document.getElementById( const rootEl = panel.panelWindow.document.getElementById(
"style-editor-chrome" "style-editor-chrome"

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

@ -45,6 +45,8 @@ add_task(async function() {
content.console.log(msg); content.console.log(msg);
}); });
await onMessage; await onMessage;
await hud.toolbox.sourceMapURLService.waitForSourcesLoading();
} }
const onConsolesDestroyed = waitForNEvents("web-console-destroyed", 4); const onConsolesDestroyed = waitForNEvents("web-console-destroyed", 4);

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

@ -33,6 +33,12 @@ loader.lazyRequireGetter(
"devtools/shared/layout/utils", "devtools/shared/layout/utils",
true true
); );
loader.lazyRequireGetter(
this,
["UPDATE_GENERAL", "UPDATE_PRESERVING_RULES"],
"devtools/server/actors/stylesheets",
true
);
const TRANSITION_PSEUDO_CLASS = ":-moz-styleeditor-transitioning"; const TRANSITION_PSEUDO_CLASS = ":-moz-styleeditor-transitioning";
const TRANSITION_DURATION_MS = 500; const TRANSITION_DURATION_MS = 500;
@ -49,14 +55,19 @@ const TRANSITION_SHEET =
} }
`); `);
// If the user edits a stylesheet, we stash a copy of the edited text
// here, keyed by the stylesheet. This way, if the tools are closed
// and then reopened, the edited text will be available. A weak map
// is used so that navigation by the user will eventually cause the
// edited text to be collected.
const modifiedStyleSheets = new WeakMap();
class StyleSheetWatcher { class StyleSheetWatcher {
constructor() { constructor() {
this._resourceCount = 0; this._resourceCount = 0;
// The _styleSheetMap maps resouceId and following value. // The _styleSheetMap maps resourceId and following value.
// { // {
// styleSheet: Raw StyleSheet object. // styleSheet: Raw StyleSheet object.
// modifiedText: Content of the stylesheet updated by update function.
// In case not updating, this value is undefined.
// } // }
this._styleSheetMap = new Map(); this._styleSheetMap = new Map();
// List of all watched media queries. Change listeners are being registered from _getMediaRules. // List of all watched media queries. Change listeners are being registered from _getMediaRules.
@ -97,11 +108,7 @@ class StyleSheetWatcher {
styleSheets.push(...(await this._getStyleSheets(window))); styleSheets.push(...(await this._getStyleSheets(window)));
} }
this._onAvailable( await this._notifyResourcesAvailable(styleSheets);
await Promise.all(
styleSheets.map(styleSheet => this._toResource(styleSheet))
)
);
} }
/** /**
@ -129,11 +136,45 @@ class StyleSheetWatcher {
// This triggers StyleSheetApplicableStateChanged event. // This triggers StyleSheetApplicableStateChanged event.
parent.appendChild(style); parent.appendChild(style);
// This promise will be resolved when the resource for this stylesheet is available.
let resolve = null;
const promise = new Promise(r => {
resolve = r;
});
if (!this._stylesheetCreationData) { if (!this._stylesheetCreationData) {
this._stylesheetCreationData = new WeakMap(); this._stylesheetCreationData = new WeakMap();
} }
// style.sheet will be available after the style element is appneded. this._stylesheetCreationData.set(style.sheet, {
this._stylesheetCreationData.set(style.sheet, { fileName }); isCreatedByDevTools: true,
fileName,
resolve,
});
await promise;
return style.sheet;
}
async ensureResourceAvailable(styleSheet) {
if (this.getResourceId(styleSheet)) {
return;
}
await this._notifyResourcesAvailable([styleSheet]);
}
/**
* Return resourceId of the given style sheet.
*/
getResourceId(styleSheet) {
for (const [resourceId, value] of this._styleSheetMap.entries()) {
if (styleSheet === value.styleSheet) {
return resourceId;
}
}
return null;
} }
/** /**
@ -144,11 +185,29 @@ class StyleSheetWatcher {
return styleSheet.ownerNode; return styleSheet.ownerNode;
} }
/**
* Return the style sheet of the given resource id.
*/
getStyleSheet(resourceId) {
const { styleSheet } = this._styleSheetMap.get(resourceId);
return styleSheet;
}
/**
* Return the index of given stylesheet of the given resource id.
*/
getStyleSheetIndex(resourceId) {
const { styleSheet } = this._styleSheetMap.get(resourceId);
return this._getStyleSheetIndex(styleSheet);
}
/** /**
* Protocol method to get the text of stylesheet of resourceId. * Protocol method to get the text of stylesheet of resourceId.
*/ */
async getText(resourceId) { async getText(resourceId) {
const { styleSheet, modifiedText } = this._styleSheetMap.get(resourceId); const { styleSheet } = this._styleSheetMap.get(resourceId);
const modifiedText = modifiedStyleSheets.get(styleSheet);
// modifiedText is the content of the stylesheet updated by update function. // modifiedText is the content of the stylesheet updated by update function.
// In case not updating, this is undefined. // In case not updating, this is undefined.
@ -181,27 +240,33 @@ class StyleSheetWatcher {
/** /**
* Update the style sheet in place with new text. * Update the style sheet in place with new text.
* *
* @param {object} request * @param {String} resourceId
* 'text' - new text * @param {String} text
* 'transition' - whether to do CSS transition for change. * New text.
* @param {Boolean} transition
* Whether to do CSS transition for change.
* @param {Number} kind
* Either UPDATE_PRESERVING_RULES or UPDATE_GENERAL
*/ */
async update(resourceId, text, transition) { async update(resourceId, text, transition, kind = UPDATE_GENERAL) {
const { styleSheet } = this._styleSheetMap.get(resourceId); const { styleSheet } = this._styleSheetMap.get(resourceId);
InspectorUtils.parseStyleSheet(styleSheet, text); InspectorUtils.parseStyleSheet(styleSheet, text);
modifiedStyleSheets.set(styleSheet, text);
this._styleSheetMap.set(resourceId, { styleSheet, modifiedText: text }); if (kind !== UPDATE_PRESERVING_RULES) {
this._notifyPropertyChanged(
this._notifyPropertyChanged( resourceId,
resourceId, "ruleCount",
"ruleCount", styleSheet.cssRules.length
styleSheet.cssRules.length );
); }
if (transition) { if (transition) {
this._startTransition(resourceId); this._startTransition(resourceId, kind);
} else { } else {
this._updateResource(resourceId, "style-applied"); this._notifyResourceUpdated(resourceId, "style-applied", {
event: { kind },
});
} }
// Remove event handler from all media query list we set to. // Remove event handler from all media query list we set to.
@ -210,10 +275,12 @@ class StyleSheetWatcher {
} }
const mediaRules = await this._getMediaRules(resourceId, styleSheet); const mediaRules = await this._getMediaRules(resourceId, styleSheet);
this._updateResource(resourceId, "media-rules-changed", { mediaRules }); this._notifyResourceUpdated(resourceId, "media-rules-changed", {
resourceUpdates: { mediaRules },
});
} }
_startTransition(resourceId) { _startTransition(resourceId, kind) {
const { styleSheet } = this._styleSheetMap.get(resourceId); const { styleSheet } = this._styleSheetMap.get(resourceId);
const document = styleSheet.ownerNode.ownerDocument; const document = styleSheet.ownerNode.ownerDocument;
const window = styleSheet.ownerNode.ownerGlobal; const window = styleSheet.ownerNode.ownerGlobal;
@ -232,18 +299,20 @@ class StyleSheetWatcher {
// @see _onTransitionEnd // @see _onTransitionEnd
window.clearTimeout(this._transitionTimeout); window.clearTimeout(this._transitionTimeout);
this._transitionTimeout = window.setTimeout( this._transitionTimeout = window.setTimeout(
this._onTransitionEnd.bind(this, resourceId), this._onTransitionEnd.bind(this, resourceId, kind),
TRANSITION_DURATION_MS + TRANSITION_BUFFER_MS TRANSITION_DURATION_MS + TRANSITION_BUFFER_MS
); );
} }
_onTransitionEnd(resourceId) { _onTransitionEnd(resourceId, kind) {
const { styleSheet } = this._styleSheetMap.get(resourceId); const { styleSheet } = this._styleSheetMap.get(resourceId);
const document = styleSheet.ownerNode.ownerDocument; const document = styleSheet.ownerNode.ownerDocument;
this._transitionTimeout = null; this._transitionTimeout = null;
removePseudoClassLock(document.documentElement, TRANSITION_PSEUDO_CLASS); removePseudoClassLock(document.documentElement, TRANSITION_PSEUDO_CLASS);
this._updateResource(resourceId, "style-applied"); this._notifyResourceUpdated(resourceId, "style-applied", {
event: { kind },
});
} }
async _fetchStylesheet(styleSheet) { async _fetchStylesheet(styleSheet) {
@ -395,19 +464,14 @@ class StyleSheetWatcher {
} }
_onMatchesChange(resourceId, index, mql) { _onMatchesChange(resourceId, index, mql) {
this._onUpdated([ this._notifyResourceUpdated(resourceId, "matches-change", {
{ nestedResourceUpdates: [
resourceType: STYLESHEET, {
resourceId, path: ["mediaRules", index, "matches"],
updateType: "matches-change", value: mql.matches,
nestedResourceUpdates: [ },
{ ],
path: ["mediaRules", index, "matches"], });
value: mql.matches,
},
],
},
]);
} }
_getNodeHref(styleSheet) { _getNodeHref(styleSheet) {
@ -495,8 +559,8 @@ class StyleSheetWatcher {
} }
_notifyPropertyChanged(resourceId, property, value) { _notifyPropertyChanged(resourceId, property, value) {
this._updateResource(resourceId, "property-change", { this._notifyResourceUpdated(resourceId, "property-change", {
[property]: value, resourceUpdates: { [property]: value },
}); });
} }
@ -519,7 +583,7 @@ class StyleSheetWatcher {
*/ */
async _onApplicableStateChanged({ applicable, stylesheet: styleSheet }) { async _onApplicableStateChanged({ applicable, stylesheet: styleSheet }) {
for (const existing of this._styleSheetMap.values()) { for (const existing of this._styleSheetMap.values()) {
if (styleSheet === existing.styleSheet) { if (existing.styleSheet === styleSheet) {
return; return;
} }
} }
@ -532,14 +596,7 @@ class StyleSheetWatcher {
this._shouldListSheet(styleSheet) && this._shouldListSheet(styleSheet) &&
!this._haveAncestorWithSameURL(styleSheet) !this._haveAncestorWithSameURL(styleSheet)
) { ) {
const creationData = this._stylesheetCreationData?.get(styleSheet); await this._notifyResourcesAvailable([styleSheet]);
this._onAvailable([
await this._toResource(styleSheet, {
isCreatedByDevTools: !!creationData,
fileName: creationData?.fileName,
}),
]);
this._stylesheetCreationData?.delete(styleSheet);
} }
} }
@ -559,6 +616,7 @@ class StyleSheetWatcher {
{ isCreatedByDevTools = false, fileName = null } = {} { isCreatedByDevTools = false, fileName = null } = {}
) { ) {
const resourceId = `stylesheet:${this._resourceCount++}`; const resourceId = `stylesheet:${this._resourceCount++}`;
const resource = { const resource = {
resourceId, resourceId,
resourceType: STYLESHEET, resourceType: STYLESHEET,
@ -576,18 +634,46 @@ class StyleSheetWatcher {
title: styleSheet.title, title: styleSheet.title,
}; };
this._styleSheetMap.set(resource.resourceId, { styleSheet });
return resource; return resource;
} }
_updateResource(resourceId, updateType, resourceUpdates) { async _notifyResourcesAvailable(styleSheets) {
const resources = await Promise.all(
styleSheets.map(async styleSheet => {
const creationData = this._stylesheetCreationData?.get(styleSheet);
const resource = await this._toResource(styleSheet, {
isCreatedByDevTools: creationData?.isCreatedByDevTools,
fileName: creationData?.fileName,
});
this._styleSheetMap.set(resource.resourceId, { styleSheet });
return resource;
})
);
await this._onAvailable(resources);
for (const styleSheet of styleSheets) {
const creationData = this._stylesheetCreationData?.get(styleSheet);
creationData?.resolve();
this._stylesheetCreationData?.delete(styleSheet);
}
}
_notifyResourceUpdated(
resourceId,
updateType,
{ resourceUpdates, nestedResourceUpdates, event }
) {
this._onUpdated([ this._onUpdated([
{ {
resourceType: STYLESHEET, resourceType: STYLESHEET,
resourceId, resourceId,
updateType, updateType,
resourceUpdates, resourceUpdates,
nestedResourceUpdates,
event,
}, },
]); ]);
} }

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

@ -19,6 +19,11 @@ const {
ELEMENT_STYLE, ELEMENT_STYLE,
} = require("devtools/shared/specs/styles"); } = require("devtools/shared/specs/styles");
const {
TYPES,
getResourceWatcher,
} = require("devtools/server/actors/resources/index");
loader.lazyRequireGetter( loader.lazyRequireGetter(
this, this,
"CssLogic", "CssLogic",
@ -126,6 +131,20 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
this._observedRules = []; this._observedRules = [];
this._styleApplied = this._styleApplied.bind(this); this._styleApplied = this._styleApplied.bind(this);
this._watchedSheets = new Set(); this._watchedSheets = new Set();
const watcher = getResourceWatcher(
this.inspector.targetActor,
TYPES.STYLESHEET
);
if (watcher) {
this.styleSheetWatcher = watcher;
this.onResourceUpdated = this.onResourceUpdated.bind(this);
this.inspector.targetActor.on(
"resource-updated-form",
this.onResourceUpdated
);
}
}, },
destroy: function() { destroy: function() {
@ -193,7 +212,7 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
// the keyframe cache. // the keyframe cache.
this.cssLogic.reset(); this.cssLogic.reset();
if (kind === UPDATE_GENERAL) { if (kind === UPDATE_GENERAL) {
this.emit("stylesheet-updated", styleSheet); this.emit("stylesheet-updated");
} }
}, },
@ -235,6 +254,14 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
* The actor for this style sheet * The actor for this style sheet
*/ */
_sheetRef: function(sheet) { _sheetRef: function(sheet) {
if (this.styleSheetWatcher) {
// We need to clean up this function in bug 1672090 when server-side stylesheet
// watcher is enabled.
console.warn(
"This function should not be called when server-side stylesheet watcher is enabled"
);
}
const targetActor = this.inspector.targetActor; const targetActor = this.inspector.targetActor;
const actor = targetActor.createStyleSheetActor(sheet); const actor = targetActor.createStyleSheetActor(sheet);
return actor; return actor;
@ -953,6 +980,15 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
ruleSet.add(parent); ruleSet.add(parent);
} }
} }
}
// We need to clean up the following codes when server-side stylesheet
// watcher is enabled in bug 1672090.
if (this.styleSheetWatcher) {
return;
}
for (const rule of ruleSet) {
if (rule.rawRule.parentStyleSheet) { if (rule.rawRule.parentStyleSheet) {
const parent = this._sheetRef(rule.rawRule.parentStyleSheet); const parent = this._sheetRef(rule.rawRule.parentStyleSheet);
if (!sheetSet.has(parent)) { if (!sheetSet.has(parent)) {
@ -1074,6 +1110,36 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
} }
}, },
onResourceUpdated(resources) {
const kinds = new Set();
for (const resource of resources) {
if (resource.resourceType !== TYPES.STYLESHEET) {
continue;
}
if (resource.updateType !== "style-applied") {
continue;
}
const kind = resource.event.kind;
kinds.add(kind);
for (const styleActor of [...this.refMap.values()]) {
const resourceId = this.styleSheetWatcher.getResourceId(
styleActor._parentSheet
);
if (resource.resourceId === resourceId) {
styleActor._onStyleApplied(kind);
}
}
}
for (const kind of kinds) {
this._styleApplied(kind);
}
},
/** /**
* Helper function to addNewRule to get or create a style tag in the provided * Helper function to addNewRule to get or create a style tag in the provided
* document. * document.
@ -1116,8 +1182,20 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
* @returns {StyleRuleActor} the new rule * @returns {StyleRuleActor} the new rule
*/ */
async addNewRule(node, pseudoClasses) { async addNewRule(node, pseudoClasses) {
const style = this.getStyleElement(node.rawNode.ownerDocument); let sheet = null;
const sheet = style.sheet; if (this.styleSheetWatcher) {
const doc = node.rawNode.ownerDocument;
if (this.styleElements.has(doc)) {
sheet = this.styleElements.get(doc);
} else {
sheet = await this.styleSheetWatcher.addStyleSheet(doc);
this.styleElements.set(doc, sheet);
}
} else {
const style = this.getStyleElement(node.rawNode.ownerDocument);
sheet = style.sheet;
}
const cssRules = sheet.cssRules; const cssRules = sheet.cssRules;
const rawNode = node.rawNode; const rawNode = node.rawNode;
const classes = [...rawNode.classList]; const classes = [...rawNode.classList];
@ -1137,12 +1215,19 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
const index = sheet.insertRule(selector + " {}", cssRules.length); const index = sheet.insertRule(selector + " {}", cssRules.length);
// If inserting the rule succeeded, go ahead and edit the source if (this.styleSheetWatcher) {
// text if requested. const resourceId = this.styleSheetWatcher.getResourceId(sheet);
const sheetActor = this._sheetRef(sheet); let authoredText = await this.styleSheetWatcher.getText(resourceId);
let { str: authoredText } = await sheetActor.getText(); authoredText += "\n" + selector + " {\n" + "}";
authoredText += "\n" + selector + " {\n" + "}"; await this.styleSheetWatcher.update(resourceId, authoredText, false);
await sheetActor.update(authoredText, false); } else {
// If inserting the rule succeeded, go ahead and edit the source
// text if requested.
const sheetActor = this._sheetRef(sheet);
let { str: authoredText } = await sheetActor.getText();
authoredText += "\n" + selector + " {\n" + "}";
await sheetActor.update(authoredText, false);
}
const cssRule = sheet.cssRules.item(index); const cssRule = sheet.cssRules.item(index);
const ruleActor = this._styleRef(cssRule); const ruleActor = this._styleRef(cssRule);
@ -1384,8 +1469,10 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
this.line = InspectorUtils.getRelativeRuleLine(this.rawRule); this.line = InspectorUtils.getRelativeRuleLine(this.rawRule);
this.column = InspectorUtils.getRuleColumn(this.rawRule); this.column = InspectorUtils.getRuleColumn(this.rawRule);
this._parentSheet = this.rawRule.parentStyleSheet; this._parentSheet = this.rawRule.parentStyleSheet;
this.sheetActor = this.pageStyle._sheetRef(this._parentSheet); if (!this.pageStyle.styleSheetWatcher) {
this.sheetActor.on("style-applied", this._onStyleApplied); this.sheetActor = this.pageStyle._sheetRef(this._parentSheet);
this.sheetActor.on("style-applied", this._onStyleApplied);
}
} }
} else { } else {
// Fake a rule // Fake a rule
@ -1434,7 +1521,7 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
// If a rule has been modified via CSSOM, then we should fall // If a rule has been modified via CSSOM, then we should fall
// back to non-authored editing. // back to non-authored editing.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1224121 // https://bugzilla.mozilla.org/show_bug.cgi?id=1224121
!this.sheetActor.hasRulesModifiedByCSSOM() && !InspectorUtils.hasRulesModifiedByCSSOM(this._parentSheet) &&
// Special case about:PreferenceStyleSheet, as it is generated on // Special case about:PreferenceStyleSheet, as it is generated on
// the fly and the URI is not registered with the about:handler // the fly and the URI is not registered with the about:handler
// https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37 // https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37
@ -1526,18 +1613,36 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
this.type === CSSRule.KEYFRAME_RULE this.type === CSSRule.KEYFRAME_RULE
? this.rawRule.keyText ? this.rawRule.keyText
: this.rawRule.selectorText; : this.rawRule.selectorText;
data.source = {
// Inline stylesheets have a null href; Use window URL instead.
type: this.sheetActor.href ? "stylesheet" : "inline",
href:
this.sheetActor.href || this.sheetActor.window.location.toString(),
id: this.sheetActor.actorID,
index: this.sheetActor.styleSheetIndex,
// Whether the stylesheet lives in a different frame than the host document.
isFramed: this.sheetActor.ownerWindow !== this.sheetActor.window,
};
// Used to differentiate between changes to rules with identical selectors. // Used to differentiate between changes to rules with identical selectors.
data.ruleIndex = this._ruleIndex; data.ruleIndex = this._ruleIndex;
if (this.pageStyle.styleSheetWatcher) {
const watcher = this.pageStyle.styleSheetWatcher;
const sheet = this._parentSheet;
const inspectorActor = this.pageStyle.inspector;
const resourceId = watcher.getResourceId(sheet);
const styleSheetIndex = watcher.getStyleSheetIndex(resourceId);
data.source = {
// Inline stylesheets have a null href; Use window URL instead.
type: sheet.href ? "stylesheet" : "inline",
href: sheet.href || inspectorActor.window.location.toString(),
id: resourceId,
index: styleSheetIndex,
// Whether the stylesheet lives in a different frame than the host document.
isFramed: inspectorActor.window !== inspectorActor.window.top,
};
} else {
data.source = {
// Inline stylesheets have a null href; Use window URL instead.
type: this.sheetActor.href ? "stylesheet" : "inline",
href:
this.sheetActor.href || this.sheetActor.window.location.toString(),
id: this.sheetActor.actorID,
index: this.sheetActor.styleSheetIndex,
// Whether the stylesheet lives in a different frame than the host document.
isFramed: this.sheetActor.ownerWindow !== this.sheetActor.window,
};
}
} }
return data; return data;
@ -1594,9 +1699,15 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
} }
} }
if (this._parentSheet) { if (this._parentSheet) {
form.parentStyleSheet = this.pageStyle._sheetRef( if (this.pageStyle.styleSheetWatcher) {
this._parentSheet form.parentStyleSheet = this.pageStyle.styleSheetWatcher.getResourceId(
).actorID; this._parentSheet
);
} else {
form.parentStyleSheet = this.pageStyle._sheetRef(
this._parentSheet
).actorID;
}
} }
// One tricky thing here is that other methods in this actor must // One tricky thing here is that other methods in this actor must
@ -1798,10 +1909,13 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
// the rules. Now, recompute our new rule from the style sheet, // the rules. Now, recompute our new rule from the style sheet,
// so that we aren't left with a reference to a dangling rule. // so that we aren't left with a reference to a dangling rule.
const oldRule = this.rawRule; const oldRule = this.rawRule;
const oldActor = this.pageStyle.refMap.get(oldRule);
this.rawRule = this._getRuleFromIndex(this._parentSheet); this.rawRule = this._getRuleFromIndex(this._parentSheet);
// Also tell the page style so that future calls to _styleRef if (oldActor) {
// return the same StyleRuleActor. // Also tell the page style so that future calls to _styleRef
this.pageStyle.updateStyleRef(oldRule, this.rawRule, this); // return the same StyleRuleActor.
this.pageStyle.updateStyleRef(oldRule, this.rawRule, this);
}
const line = InspectorUtils.getRelativeRuleLine(this.rawRule); const line = InspectorUtils.getRelativeRuleLine(this.rawRule);
const column = InspectorUtils.getRuleColumn(this.rawRule); const column = InspectorUtils.getRuleColumn(this.rawRule);
if (line !== this.line || column !== this.column) { if (line !== this.line || column !== this.column) {
@ -1825,7 +1939,7 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
* ignore it and parse the stylehseet again. The authoredText * ignore it and parse the stylehseet again. The authoredText
* may be outdated if a descendant of this rule has changed. * may be outdated if a descendant of this rule has changed.
*/ */
getAuthoredCssText: function(skipCache = false) { getAuthoredCssText: async function(skipCache = false) {
if (!this.canSetRuleText || !SUPPORTED_RULE_TYPES.includes(this.type)) { if (!this.canSetRuleText || !SUPPORTED_RULE_TYPES.includes(this.type)) {
return Promise.resolve(""); return Promise.resolve("");
} }
@ -1834,6 +1948,23 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
return Promise.resolve(this.authoredText); return Promise.resolve(this.authoredText);
} }
if (this.pageStyle.styleSheetWatcher) {
await this.pageStyle.styleSheetWatcher.ensureResourceAvailable(
this._parentSheet
);
const resourceId = this.pageStyle.styleSheetWatcher.getResourceId(
this._parentSheet
);
const cssText = await this.pageStyle.styleSheetWatcher.getText(
resourceId
);
const { text } = getRuleText(cssText, this.line, this.column);
// Cache the result on the rule actor to avoid parsing again next time
this.authoredText = text;
return this.authoredText;
}
return this.sheetActor.getText().then(longStr => { return this.sheetActor.getText().then(longStr => {
const cssText = longStr.str; const cssText = longStr.str;
const { text } = getRuleText(cssText, this.line, this.column); const { text } = getRuleText(cssText, this.line, this.column);
@ -1874,7 +2005,23 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
} else { } else {
// Get the rule's authored text and skip any cached value. // Get the rule's authored text and skip any cached value.
ruleBodyText = await this.getAuthoredCssText(true); ruleBodyText = await this.getAuthoredCssText(true);
const { str: stylesheetText } = await this.sheetActor.getText();
let stylesheetText = null;
if (this.pageStyle.styleSheetWatcher) {
await this.pageStyle.styleSheetWatcher.ensureResourceAvailable(
this._parentSheet
);
const resourceId = this.pageStyle.styleSheetWatcher.getResourceId(
this._parentSheet
);
stylesheetText = await this.pageStyle.styleSheetWatcher.getText(
resourceId
);
} else {
const { str } = await this.sheetActor.getText();
stylesheetText = str;
}
const [start, end] = getSelectorOffsets( const [start, end] = getSelectorOffsets(
stylesheetText, stylesheetText,
this.line, this.line,
@ -1932,6 +2079,27 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
if (this.type === ELEMENT_STYLE) { if (this.type === ELEMENT_STYLE) {
// For element style rules, set the node's style attribute. // For element style rules, set the node's style attribute.
this.rawNode.setAttributeDevtools("style", newText); this.rawNode.setAttributeDevtools("style", newText);
} else if (this.pageStyle.styleSheetWatcher) {
await this.pageStyle.styleSheetWatcher.ensureResourceAvailable(
this._parentSheet
);
const resourceId = this.pageStyle.styleSheetWatcher.getResourceId(
this._parentSheet
);
let cssText = await this.pageStyle.styleSheetWatcher.getText(resourceId);
const { offset, text } = getRuleText(cssText, this.line, this.column);
cssText =
cssText.substring(0, offset) +
newText +
cssText.substring(offset + text.length);
await this.pageStyle.styleSheetWatcher.update(
resourceId,
cssText,
false,
UPDATE_PRESERVING_RULES
);
} else { } else {
// For stylesheet rules, set the text in the stylesheet. // For stylesheet rules, set the text in the stylesheet.
const parentStyleSheet = this.pageStyle._sheetRef(this._parentSheet); const parentStyleSheet = this.pageStyle._sheetRef(this._parentSheet);
@ -2040,18 +2208,49 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
return null; return null;
} }
const sheetActor = this.pageStyle._sheetRef(parentStyleSheet); if (this.pageStyle.styleSheetWatcher) {
let { str: authoredText } = await sheetActor.getText(); await this.pageStyle.styleSheetWatcher.ensureResourceAvailable(
const [startOffset, endOffset] = getSelectorOffsets( this._parentSheet
authoredText, );
this.line, const resourceId = this.pageStyle.styleSheetWatcher.getResourceId(
this.column this._parentSheet
); );
authoredText = let authoredText = await this.pageStyle.styleSheetWatcher.getText(
authoredText.substring(0, startOffset) + resourceId
value + );
authoredText.substring(endOffset);
await sheetActor.update(authoredText, false, UPDATE_PRESERVING_RULES); const [startOffset, endOffset] = getSelectorOffsets(
authoredText,
this.line,
this.column
);
authoredText =
authoredText.substring(0, startOffset) +
value +
authoredText.substring(endOffset);
await this.pageStyle.styleSheetWatcher.update(
resourceId,
authoredText,
false,
UPDATE_PRESERVING_RULES
);
} else {
const sheetActor = this.pageStyle._sheetRef(parentStyleSheet);
let { str: authoredText } = await sheetActor.getText();
const [startOffset, endOffset] = getSelectorOffsets(
authoredText,
this.line,
this.column
);
authoredText =
authoredText.substring(0, startOffset) +
value +
authoredText.substring(endOffset);
await sheetActor.update(authoredText, false, UPDATE_PRESERVING_RULES);
}
} else { } else {
const cssRules = parentStyleSheet.cssRules; const cssRules = parentStyleSheet.cssRules;
const cssText = rule.cssText; const cssText = rule.cssText;

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

@ -57,6 +57,19 @@ class ResourceWatcher {
return this._cache.filter(r => r.resourceType === resourceType); return this._cache.filter(r => r.resourceType === resourceType);
} }
/**
* Return the specified resource cached in this watcher.
*
* @param {String} resourceType
* @param {String} resourceId
* @return {Object} resource cached in this watcher
*/
getResourceById(resourceType, resourceId) {
return this._cache.find(
r => r.resourceType === resourceType && r.resourceId === resourceId
);
}
/** /**
* Request to start retrieving all already existing instances of given * Request to start retrieving all already existing instances of given
* type of resources and also start watching for the one to be created after. * type of resources and also start watching for the one to be created after.

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

@ -96,7 +96,6 @@ const pageStyleSpec = generateActorSpec({
events: { events: {
"stylesheet-updated": { "stylesheet-updated": {
type: "styleSheetUpdated", type: "styleSheetUpdated",
styleSheet: Arg(0, "stylesheet"),
}, },
}, },