Skip to content

Commit

Permalink
process mutations in bulk
Browse files Browse the repository at this point in the history
When eIds are re-written, we can have hundreds of attribute modification
mutations. These are slow when processed individually. Instead, we
process them as a group. After we find first mutation we find in the list that
impacts a component, we can stop processing the remainder.

In the common case, bulk attribute modifications happen after the tree
is modified by a node change; so the first change in the list is a node
change. In many cases this causes the editors to update and so the
attribute modifications are ignored completely.
  • Loading branch information
longhotsummer committed Feb 7, 2025
1 parent ddac8b4 commit aa8314c
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 75 deletions.
8 changes: 3 additions & 5 deletions indigo_app/static/javascript/indigo/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* This model fires custom events:
*
* - mutation - when the XML DOM is manipulated by any means, based on the MutationObserver class. The parameter
* for the event is a MutationRecord object.
* for the event is list of MutationRecord objects.
* - change:dom - when the XML DOM is manipulated by any means, after all the mutation events have been fired.
*/
Indigo.DocumentContent = Backbone.Model.extend({
Expand All @@ -47,10 +47,8 @@

setupMutationObserver: function () {
this.observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
console.log('mutation', mutation);
this.trigger('mutation', this, mutation);
}
console.log('mutations', mutations);
this.trigger('mutation', this, mutations);
this.trigger('change:dom', this);
this.trigger('change', this);
});
Expand Down
77 changes: 40 additions & 37 deletions indigo_app/static/javascript/indigo/views/document_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,49 +189,52 @@
* The XML document has changed, re-render if it impacts our xmlElement.
*
* @param model documentContent model
* @param mutation a MutationRecord object
* @param mutations an array of MutationRecord objects
*/
onDomMutated: function(model, mutation) {
let eid = mutation.target.getAttribute ? mutation.target.getAttribute('eId') : null;

switch (model.getMutationImpact(mutation, this.xmlElement)) {
case 'replaced':
this.xmlElement = mutation.addedNodes[0];
this.render();
break;
case 'changed':
if (this.quickEditEid && eid !== this.quickEditEid) {
// Check for the special case of the quick-edited element being replaced, so that we can re-render
// just that element (if possible). In this case the mutation target is the parent and its children have
// changed.

if (mutation.type === 'childList' && mutation.removedNodes.length === 1 &&
mutation.removedNodes[0]?.getAttribute('eId') === this.quickEditEid) {
// the quick-edited element was removed or replaced

if (mutation.addedNodes.length === 1) {
if (mutation.addedNodes[0]?.getAttribute('eId') === this.quickEditEid) {
// it was replaced but retained the eid
eid = this.quickEditEid;
onDomMutated: function(model, mutations) {
// process each mutation in order; we stop processing after finding the first one that significantly impacts us
for (const mutation of mutations) {
let eid = mutation.target.getAttribute ? mutation.target.getAttribute('eId') : null;

switch (model.getMutationImpact(mutation, this.xmlElement)) {
case 'replaced':
this.xmlElement = mutation.addedNodes[0];
this.render();
return;
case 'changed':
if (this.quickEditEid && eid !== this.quickEditEid) {
// Check for the special case of the quick-edited element being replaced, so that we can re-render
// just that element (if possible). In this case the mutation target is the parent and its children have
// changed.

if (mutation.type === 'childList' && mutation.removedNodes.length === 1 &&
mutation.removedNodes[0]?.getAttribute('eId') === this.quickEditEid) {
// the quick-edited element was removed or replaced

if (mutation.addedNodes.length === 1) {
if (mutation.addedNodes[0]?.getAttribute('eId') === this.quickEditEid) {
// it was replaced but retained the eid
eid = this.quickEditEid;
} else {
// it was replaced with a different eid; re-render the target but track the new eid
this.quickEditEid = mutation.addedNodes[0].getAttribute('eId');
}
} else {
// it was replaced with a different eid; re-render the target but track the new eid
this.quickEditEid = mutation.addedNodes[0].getAttribute('eId');
// it was removed
this.quickEditEid = null;
}
} else {
// it was removed
this.quickEditEid = null;
}
}
}

// eid can be null, in which case everything is re-rendered
this.render(eid);
break;
case 'removed':
// the change removed xmlElement from the tree
console.log('Mutation removes SourceEditor.xmlElement from the tree');
this.discardChanges();
break;
// eid can be null, in which case everything is re-rendered
this.render(eid);
return;
case 'removed':
// the change removed xmlElement from the tree
console.log('Mutation removes SourceEditor.xmlElement from the tree');
this.discardChanges();
return;
}
}
},

Expand Down
41 changes: 22 additions & 19 deletions indigo_app/static/javascript/indigo/views/document_xml_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,28 +272,31 @@ class AknTextEditor {
* The XML document has changed, re-render if it impacts our xmlElement.
*
* @param model documentContent model
* @param mutation a MutationRecord object
* @param mutations an array of MutationRecord objects
*/
onDocumentMutated (model, mutation) {
onDocumentMutated (model, mutations) {
if (!this.editing) return;

switch (model.getMutationImpact(mutation, this.xmlElement)) {
case 'replaced':
this.xmlElement = mutation.addedNodes[0];
// fall through to 'changed'
case 'changed':
if (!this.updating) {
// the XML has changed, update the text in the editor
this.previousText = this.unparse();
const posn = this.monacoEditor.getPosition();
this.monacoEditor.setValue(this.previousText);
this.monacoEditor.setPosition(posn);
}
break;
case 'removed':
console.log('Mutation removes AknTextEditor.xmlElement from the tree');
this.discardChanges();
break;
// process each mutation in order; we stop processing after finding the first one that significantly impacts us
for (const mutation of mutations) {
switch (model.getMutationImpact(mutation, this.xmlElement)) {
case 'replaced':
this.xmlElement = mutation.addedNodes[0];
// fall through to 'changed'
case 'changed':
if (!this.updating) {
// the XML has changed, update the text in the editor
this.previousText = this.unparse();
const posn = this.monacoEditor.getPosition();
this.monacoEditor.setValue(this.previousText);
this.monacoEditor.setPosition(posn);
}
return;
case 'removed':
console.log('Mutation removes AknTextEditor.xmlElement from the tree');
this.discardChanges();
return;
}
}
}

Expand Down
31 changes: 17 additions & 14 deletions indigo_app/static/javascript/indigo/views/table_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,26 @@
* The XML document has changed, re-render if it impacts our table element.
*
* @param model documentContent model
* @param mutation a MutationRecord object
* @param mutations an array of MutationRecord objects
*/
onDomMutated (model, mutation) {
onDomMutated (model, mutations) {
if (!this.editing) return;

switch (model.getMutationImpact(mutation, this.table)) {
case 'replaced':
this.discardChanges(true);
break;
case 'changed':
this.discardChanges(true);
break;
case 'removed':
// the change removed xmlElement from the tree
console.log('Mutation removes TableEditor.table from the tree');
this.discardChanges(true);
break;
// process each mutation in order; we stop processing after finding the first one that significantly impacts us
for (const mutation of mutations) {
switch (model.getMutationImpact(mutation, this.table)) {
case 'replaced':
this.discardChanges(true);
return;
case 'changed':
this.discardChanges(true);
return;
case 'removed':
// the change removed xmlElement from the tree
console.log('Mutation removes TableEditor.table from the tree');
this.discardChanges(true);
return;
}
}
},

Expand Down

0 comments on commit aa8314c

Please sign in to comment.