Skip to content

Commit

Permalink
Bug 1644193: Implement update function. r=ochameau
Browse files Browse the repository at this point in the history
Depends on D88540

Differential Revision: https://phabricator.services.mozilla.com/D89277
  • Loading branch information
Daisuke Akatsuka committed Sep 11, 2020
1 parent 3908e7a commit 1a2e261
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 7 deletions.
145 changes: 138 additions & 7 deletions devtools/server/actors/resources/stylesheets.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,46 @@ loader.lazyRequireGetter(
"devtools/shared/inspector/css-logic"
);

loader.lazyRequireGetter(
this,
["addPseudoClassLock", "removePseudoClassLock"],
"devtools/server/actors/highlighters/utils/markup",
true
);
loader.lazyRequireGetter(
this,
"loadSheet",
"devtools/shared/layout/utils",
true
);

const TRANSITION_PSEUDO_CLASS = ":-moz-styleeditor-transitioning";
const TRANSITION_DURATION_MS = 500;
const TRANSITION_BUFFER_MS = 1000;
const TRANSITION_RULE_SELECTOR = `:root${TRANSITION_PSEUDO_CLASS}, :root${TRANSITION_PSEUDO_CLASS} *`;
const TRANSITION_SHEET =
"data:text/css;charset=utf-8," +
encodeURIComponent(`
${TRANSITION_RULE_SELECTOR} {
transition-duration: ${TRANSITION_DURATION_MS}ms !important;
transition-delay: 0ms !important;
transition-timing-function: ease-out !important;
transition-property: all !important;
}
`);

class StyleSheetWatcher {
constructor() {
this._resourceCount = 0;
// The _styleSheetMap maps resouceId and following value.
// {
// 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();
// List of all watched media queries. Change listeners are being registered from _getMediaRules.
this._mqlList = [];
}

/**
Expand Down Expand Up @@ -62,7 +98,13 @@ class StyleSheetWatcher {
* Protocol method to get the text of stylesheet of resourceId.
*/
async getText(resourceId) {
const styleSheet = this._styleSheetMap.get(resourceId);
const { styleSheet, modifiedText } = this._styleSheetMap.get(resourceId);

// modifiedText is the content of the stylesheet updated by update function.
// In case not updating, this is undefined.
if (modifiedText !== undefined) {
return modifiedText;
}

if (!styleSheet.href) {
// this is an inline <style> sheet
Expand All @@ -78,14 +120,82 @@ class StyleSheetWatcher {
* @return {Boolean} the disabled state after toggling.
*/
toggleDisabled(resourceId) {
const styleSheet = this._styleSheetMap.get(resourceId);
const { styleSheet } = this._styleSheetMap.get(resourceId);
styleSheet.disabled = !styleSheet.disabled;

this._notifyPropertyChanged(resourceId, "disabled", styleSheet.disabled);

return styleSheet.disabled;
}

/**
* Update the style sheet in place with new text.
*
* @param {object} request
* 'text' - new text
* 'transition' - whether to do CSS transition for change.
*/
async update(resourceId, text, transition) {
const { styleSheet } = this._styleSheetMap.get(resourceId);

InspectorUtils.parseStyleSheet(styleSheet, text);

this._styleSheetMap.set(resourceId, { styleSheet, modifiedText: text });

this._notifyPropertyChanged(
resourceId,
"ruleCount",
styleSheet.cssRules.length
);

if (transition) {
this._startTransition(resourceId);
} else {
this._updateResource(resourceId, "style-applied");
}

// Remove event handler from all media query list we set to.
for (const mql of this._mqlList) {
mql.onchange = null;
}

const mediaRules = await this._getMediaRules(resourceId, styleSheet);
this._updateResource(resourceId, "media-rules-changed", { mediaRules });
}

_startTransition(resourceId) {
const { styleSheet } = this._styleSheetMap.get(resourceId);
const document = styleSheet.ownerNode.ownerDocument;
const window = styleSheet.ownerNode.ownerGlobal;

if (!this._transitionSheetLoaded) {
this._transitionSheetLoaded = true;
// We don't remove this sheet. It uses an internal selector that
// we only apply via locks, so there's no need to load and unload
// it all the time.
loadSheet(window, TRANSITION_SHEET);
}

addPseudoClassLock(document.documentElement, TRANSITION_PSEUDO_CLASS);

// Set up clean up and commit after transition duration (+buffer)
// @see _onTransitionEnd
window.clearTimeout(this._transitionTimeout);
this._transitionTimeout = window.setTimeout(
this._onTransitionEnd.bind(this, resourceId),
TRANSITION_DURATION_MS + TRANSITION_BUFFER_MS
);
}

_onTransitionEnd(resourceId) {
const { styleSheet } = this._styleSheetMap.get(resourceId);
const document = styleSheet.ownerNode.ownerDocument;

this._transitionTimeout = null;
removePseudoClassLock(document.documentElement, TRANSITION_PSEUDO_CLASS);
this._updateResource(resourceId, "style-applied");
}

async _fetchStylesheet(styleSheet) {
const href = styleSheet.href;

Expand Down Expand Up @@ -204,18 +314,22 @@ class StyleSheetWatcher {
return importedStyleSheets;
}

async _getMediaRules(styleSheet) {
async _getMediaRules(resourceId, styleSheet) {
this._mqlList = [];

const mediaRules = Array.from(await this._getCSSRules(styleSheet)).filter(
rule => rule.type === CSSRule.MEDIA_RULE
);

return mediaRules.map(rule => {
return mediaRules.map((rule, index) => {
let matches = false;

try {
const window = styleSheet.ownerNode.ownerGlobal;
const mql = window.matchMedia(rule.media.mediaText);
matches = mql.matches;
mql.onchange = this._onMatchesChange.bind(this, resourceId, index);
this._mqlList.push(mql);
} catch (e) {
// Ignored
}
Expand All @@ -230,6 +344,22 @@ class StyleSheetWatcher {
});
}

_onMatchesChange(resourceId, index, mql) {
this._onUpdated([
{
resourceType: STYLESHEET,
resourceId,
updateType: "matches-change",
nestedResourceUpdates: [
{
path: ["mediaRules", index, "matches"],
value: mql.matches,
},
],
},
]);
}

_getNodeHref(styleSheet) {
const { ownerNode } = styleSheet;
if (!ownerNode) {
Expand Down Expand Up @@ -332,12 +462,13 @@ class StyleSheetWatcher {
}

async _toResource(styleSheet) {
const resourceId = `stylesheet:${this._resourceCount++}`;
const resource = {
resourceId: `stylesheet:${this._resourceCount++}`,
resourceId,
resourceType: STYLESHEET,
disabled: styleSheet.disabled,
href: styleSheet.href,
mediaRules: await this._getMediaRules(styleSheet),
mediaRules: await this._getMediaRules(resourceId, styleSheet),
nodeHref: this._getNodeHref(styleSheet),
ruleCount: styleSheet.cssRules.length,
sourceMapBaseURL: this._getSourcemapBaseURL(styleSheet),
Expand All @@ -347,7 +478,7 @@ class StyleSheetWatcher {
title: styleSheet.title,
};

this._styleSheetMap.set(resource.resourceId, styleSheet);
this._styleSheetMap.set(resource.resourceId, { styleSheet });

return resource;
}
Expand Down
8 changes: 8 additions & 0 deletions devtools/server/actors/stylesheets.js
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,14 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, {
},

update(resourceId, text, transition) {
const styleSheetsWatcher = this._getStyleSheetsWatcher();
if (styleSheetsWatcher) {
return styleSheetsWatcher.update(resourceId, text, transition);
}

// Following code can be removed once we enable STYLESHEET resource on the watcher/server
// side by default. For now it is being preffed off and we have to support the two
// codepaths. Once enabled we will only support the stylesheet watcher codepath.
const actor = this._getStyleSheetActor(resourceId);
return actor.update(text, transition);
},
Expand Down

0 comments on commit 1a2e261

Please sign in to comment.