Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
DevTools style inspector and editor changes proposal to support authored styles and editor-inspector sync

What

A proposal for updating our style inspector and style editor.

For now it is a rough draft put together quickly. It will need to be refined a lot and will require discussions and, later, should be turned into a plan with bugs being filed.

Many of the things described below already work this way today, I just haven't had the chance yet to describe them as such. A list highlighting what exists today, what doesn't and what should be changed has yet to be created.

Why

To achieve 2 main goals:

  • support styles as authored in the style inspector,
  • apply changes made in the inspector to the editor and vice-versa.

Knowing that the solution also addresses:

  • exporting changes made in the devtools
  • perhaps more...

The proposal

Getting stylesheets

  • for style tags: read node.textContent since its inline in the page, and if devtools is opened after content js has made changes to it, these changes will be in the textContent DONE
  • for <link> tags and @imported sheets:
    • fetching from the cache or network isn't necessarily the best option as the resource might not have been cached and might have changed on the server.
    • something similar to Debugger.Source for css would be ideal.
    • css source maps information should be handled to retrieve the source file DONE

Listing stylesheets in the style editor

  • since we have the text for each stylesheet, just display this, in the editor DONE
  • syntax highlight DONE
  • tokenize to support code completion and preview tooltips DONE

Listing applied rules of a node in the inspector

  • get list of applied rules with, for each of them, the stylesheet ID it's in
    • DOMUtils.getCSSStyleRules does the trick (used in css-logic and styles actor) DONE
  • from this list, get the text for each selector
    • Instead of getting the CSS object model rule, which would have been interpreted/parsed by gecko, we want the text.
      • DOMUtils.GetRuleLine can give the start position of the rule's text in the sheet
      • Need a new DOMUtils.GetRuleEndLine function to be implemented Bug 974686
  • from the text, tokenize to extract property:value pairs (we shouldn't parse since if we use gecko's parser, it will give us only what it understands, unless, as we discussed with Cameron, we make changes to it so it parses differently for us).
  • from the property:value pairs, test which apply and which don't in order to provide that information to the user (greying out properties that aren't valid, striking those that are overriden).
    • one option for this is to apply the declaration to a dummy element and read its computed style to determine whether it was applied or not. Then also checking if the selected element has that value for this property in its computed style to determine if it was overriden or not.
    • cssLogic.getPropertyInfo seems to provide that information.
  • tokenizing into a property:value pairs model also allows to support "smart" editing as we do today (i.e. not free-flow text), and code completion and preview tooltips.
    • the inspector's rule-view could simply be a filtered-down version of the style editor, so that instead of having 2 separate UIs that have to implement all these features twice, use a single editor-based UI (with smart tabbing) that works everywhere.

Making changes to the styles in the editor

  • detect the changes (codemirror) and send them to the server (not the whole text)
  • store the changes in a diff in our stylesheet object
    • the diff should contain all changes made via the editor since the beginning of the session
    • applying the diff to the originally fetched text should result in the text seen in the editor
  • get the resulting text and apply it via DOMUtils.parseStyleSheet
    • this updates the content page's style
  • in case of source-mapped files (sass, less, ...), the resulting text can't be applied via DOMUtils.parseStyleSheet as it needs to be pre-processed into CSS before. As of today, source-mapped files can't be edited in the style editor anyway. Making them editable would require to save back to disk (using a resource mapping configuration), so that the pre-processor (in file-watching mode) could re-generate the CSS and Firefox (also in file-watching mode) could reload the file and ask for the new source map.

Making changes to the styles in the inspector

  • detect the changes made to the currently edited property or value (or multiple properties/values when pasting existing style into an editor)
  • send this over to the server with enough information so that it can store it in the diff just like the editor does (we may decide to migrate the rule-view to use codemirror too, for a consistent experience, in which case, the test changes object would be the exact same, simplifying this step)
  • get the resulting text and apply it via DOMUtils.parseStyleSheet
    • this updates the content page's style
  • concern about performance here: while in the editor, we can throttle the live preview, here in the inspector, it's needed to display changes live as you type (i.e. even before you've finished entering a value, since we have code completion, we need to preview the changes as you type).
    • need to handle live-preview differently (as it's done today): in a way that doesn't require saving the diff and calling DOMUtils.parseStyleSheet
    • ultimately applying the change should only be done when the new value is committed to the field

Syncing changes in the inspector with the editor and vice-versa - Bug 878398

  • with the changes proposed so far, the inspector and editor have to rely on the same back-end, share the same stylesheet actor instances
  • make the actor(s) observable so that both the inspector and editor can listen on changes made on the other side
  • the question is how should changes be communicated so that both tools can update?
    • the simplest is just to send a "changed" event and have the tool refresh themselves. This might be good enough since we can't use the 2 tools at the same time so the one in the back has plenty of time to refresh. Although this will create some potentially unnecessary protocol chatter.

Dealing with dynamically changed stylesheets

  • added and removed stylesheets
    • the style manager/whatever actor should listen to stylesheetadded/removed events
    • refresh the list of stylesheets on the server
      • if a stylesheet already has a non-empty diff, it should only be de-activated so that the diff can later be accessed again, either if a removed stylesheet is re-inserted later, or for exporting
    • let the tools ui know so they refresh
  • modified stylesheet content
    • this can happen when a style object is changed via the CSSOM (e.g. document.styleSheets[0].cssRules[0].setProperty("background", "red")).
      • The StyleRuleChanged event on nsIDocumentObserver should work in that case.
    • this can also happen with inline <style> tags for which the textContent is changed (modified textContent nodes, appended nodes, removed nodes)
      • for this, the style manager/whatever actor can listen to stylesheetadded/removed events since in that case, both a removed and added events will be fired
    • if the target stylesheet doesn't have a diff: let the tools ui know so they refresh
    • if the target stylesheet has a diff AND if we decide that it's a usecase we want to handle:
      • update the stylesheet object's stored text
      • somehow rebase the diff (without asking the user to merge obviously)
      • store the new diff
      • let the tools ui know so they refresh
      • AND call again DOMUtils.parseStyleSheet with the result of the diff applied to the new text

Why storing a diff? - Bug 960987

  • because the need to export what has changed while working with the editor or inspector exists and a unified diff would give us everything we need to do that.
  • because of the last usecase (dealing with modified stylesheet content). Granted, it's not a very important usecase, but a usecase we're bound to meet at some stage and for which, today, we fail.
  • because we can :) https://github.com/kpdecker/jsdiff works great and is a mere 360 lines of open source js code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment