Skip to content

Commit

Permalink
inlined _topologicalSortBindings()
Browse files Browse the repository at this point in the history
  • Loading branch information
justlep committed Sep 25, 2022
1 parent e937cdf commit 0b81b87
Showing 1 changed file with 112 additions and 113 deletions.
225 changes: 112 additions & 113 deletions src/binding/bindingAttributeSyntax.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,42 +358,6 @@ const _applyBindingsToNodeAndDescendantsInternal = (bindingContext, nodeVerified
}
};

const _topologicalSortBindings = (bindings) => {
// Depth-first sort
let result = [], // The list of key/handler pairs that we will return
bindingsConsidered = {}, // A temporary record of which bindings are already in 'result'
cyclicDependencyStack = [], // Keeps track of a depth-search so that, if there's a cycle, we know which bindings caused it
_pushBinding = bindingKey => {
if (bindingsConsidered[bindingKey]) {
return;
}
bindingsConsidered[bindingKey] = true;
let binding = getBindingHandler(bindingKey);
if (!binding) {
return;
}
// First add dependencies (if any) of the current binding
if (binding.after) {
cyclicDependencyStack.push(bindingKey);
for (let bindingDependencyKey of binding.after) {
if (bindings[bindingDependencyKey]) {
if (cyclicDependencyStack.includes(bindingDependencyKey)) {
throw Error("Cannot combine the following bindings, because they have a cyclic dependency: " + cyclicDependencyStack.join(", "));
}
_pushBinding(bindingDependencyKey);
}
}
cyclicDependencyStack.length--;
}
// Next add the current binding
result.push({key: bindingKey, handler: binding});
};

for (let bindingKey of Object.keys(bindings)) {
_pushBinding(bindingKey);
}
return result;
};

const _applyBindingsToNodeInternal = (node, sourceBindings, bindingContext) => {
let nodeDomData = _ensureNodeHasDomData(node),
Expand Down Expand Up @@ -451,95 +415,130 @@ const _applyBindingsToNodeInternal = (node, sourceBindings, bindingContext) => {
}
}

if (!bindings) {
return {
bindingContextForDescendants: bindingContext
};
}

let contextToExtend = bindingContext,
bindingHandlerThatControlsDescendantBindings;

if (bindings) {
// Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding
// context update), just return the value accessor from the binding. Otherwise, return a function that always gets
// the latest binding value and registers a dependency on the binding updater.
let getValueAccessor = bindingsUpdater ?
(bindingKey) => () => bindingsUpdater()[bindingKey]() :
(bindingKey) => bindings[bindingKey];

// let allBindings = () => {
// throw new Error('Use of allBindings as a function is no longer supported');
// };
// ^^^ using a function and add custom methods to it is 98% slower than direct object literals in Firefox 81,
// plus the 'no longer supported' message has existed since 2013.. time to drop it

// The following is the 3.x allBindings API
let allBindings = {
get: (key) => bindings[key] && getValueAccessor(key)(),
has: (key) => key in bindings
};

if (EVENT_CHILDREN_COMPLETE in bindings) {
bindingEvent.subscribe(node, EVENT_CHILDREN_COMPLETE, () => {
let callback = bindings[EVENT_CHILDREN_COMPLETE]();
if (callback) {
let nodes = childNodes(node);
if (nodes.length) {
callback(nodes, dataFor(nodes[0]));
}
}
});
}
// Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding
// context update), just return the value accessor from the binding. Otherwise, return a function that always gets
// the latest binding value and registers a dependency on the binding updater.
let getValueAccessor = bindingsUpdater ?
(bindingKey) => () => bindingsUpdater()[bindingKey]() :
(bindingKey) => bindings[bindingKey];

// let allBindings = () => {
// throw new Error('Use of allBindings as a function is no longer supported');
// };
// ^^^ using a function and add custom methods to it is 98% slower than direct object literals in Firefox 81,
// plus the 'no longer supported' message has existed since 2013.. time to drop it

// The following is the 3.x allBindings API
let allBindings = {
get: (key) => bindings[key] && getValueAccessor(key)(),
has: (key) => key in bindings
};

if (EVENT_DESCENDENTS_COMPLETE in bindings) {
contextToExtend = bindingEvent.startPossiblyAsyncContentBinding(node, bindingContext);
bindingEvent.subscribe(node, EVENT_DESCENDENTS_COMPLETE, () => {
let callback = bindings[EVENT_DESCENDENTS_COMPLETE]();
if (callback && firstChild(node)) {
callback(node);
if (EVENT_CHILDREN_COMPLETE in bindings) {
bindingEvent.subscribe(node, EVENT_CHILDREN_COMPLETE, () => {
let callback = bindings[EVENT_CHILDREN_COMPLETE]();
if (callback) {
let nodes = childNodes(node);
if (nodes.length) {
callback(nodes, dataFor(nodes[0]));
}
});
}

// First put the bindings into the right order
let orderedBindings = _topologicalSortBindings(bindings);

// Go through the sorted bindings, calling init and update for each
orderedBindings.forEach(bindingKeyAndHandler => {
// Note that topologicalSortBindings has already filtered out any nonexistent binding handlers,
// so bindingKeyAndHandler.handler will always be nonnull.
let handlerInitFn = bindingKeyAndHandler.handler.init,
handlerUpdateFn = bindingKeyAndHandler.handler.update,
bindingKey = bindingKeyAndHandler.key;
}
});
}

if (node.nodeType === 8 && !allowedVirtualElementBindings[bindingKey]) {
throw new Error("The binding '" + bindingKey + "' cannot be used with virtual elements");
if (EVENT_DESCENDENTS_COMPLETE in bindings) {
contextToExtend = bindingEvent.startPossiblyAsyncContentBinding(node, bindingContext);
bindingEvent.subscribe(node, EVENT_DESCENDENTS_COMPLETE, () => {
let callback = bindings[EVENT_DESCENDENTS_COMPLETE]();
if (callback && firstChild(node)) {
callback(node);
}
});
}

try {
// Run init, ignoring any dependencies
if (typeof handlerInitFn === 'function') {
ignoreDependencyDetectionNoArgs(() => {
let initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend.$data, contextToExtend);

// If this binding handler claims to control descendant bindings, make a note of this
if (initResult && initResult.controlsDescendantBindings) {
if (bindingHandlerThatControlsDescendantBindings) {
throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
}
bindingHandlerThatControlsDescendantBindings = bindingKey;
// First put the bindings into depth-first order (topologicalSortBindings)

let orderedBindings = [], // The list of key/handler pairs that we will return
bindingsConsidered = {}, // A temporary record of which bindings are already in 'orderedBindings'
cyclicDependencyStack = [], // Keeps track of a depth-search so that, if there's a cycle, we know which bindings caused it
_pushBinding = (bindingKey) => {
if (bindingsConsidered[bindingKey]) {
return;
}
bindingsConsidered[bindingKey] = true;
let binding = getBindingHandler(bindingKey);
if (!binding) {
return;
}
// First add dependencies (if any) of the current binding
if (binding.after) {
cyclicDependencyStack.push(bindingKey);
for (let bindingDependencyKey of binding.after) {
if (bindings[bindingDependencyKey]) {
if (cyclicDependencyStack.includes(bindingDependencyKey)) {
throw Error("Cannot combine the following bindings, because they have a cyclic dependency: " + cyclicDependencyStack.join(", "));
}
});
_pushBinding(bindingDependencyKey);
}
}
cyclicDependencyStack.length--;
}
// Next add the current binding
orderedBindings.push({key: bindingKey, handler: binding});
};

// Run update in its own computed wrapper
if (typeof handlerUpdateFn === 'function') {
dependentObservable(
() => handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend.$data, contextToExtend),
null,
{disposeWhenNodeIsRemoved: node}
);
}
} catch (ex) {
ex.message = `Unable to process binding "${bindingKey}: ${bindings[bindingKey]}"\nMessage: + ${ex.message}`;
throw ex;
for (let bindingKey of Object.keys(bindings)) {
_pushBinding(bindingKey);
}

// Go through the sorted bindings, calling init and update for each
for (let bindingKeyAndHandler of orderedBindings) {
// Note that topologicalSortBindings has already filtered out any nonexistent binding handlers,
// so bindingKeyAndHandler.handler will always be nonnull.
let handlerInitFn = bindingKeyAndHandler.handler.init,
handlerUpdateFn = bindingKeyAndHandler.handler.update,
bindingKey = bindingKeyAndHandler.key;

if (node.nodeType === 8 && !allowedVirtualElementBindings[bindingKey]) {
throw new Error("The binding '" + bindingKey + "' cannot be used with virtual elements");
}

try {
// Run init, ignoring any dependencies
if (typeof handlerInitFn === 'function') {
ignoreDependencyDetectionNoArgs(() => {
let initResult = handlerInitFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend.$data, contextToExtend);

// If this binding handler claims to control descendant bindings, make a note of this
if (initResult && initResult.controlsDescendantBindings) {
if (bindingHandlerThatControlsDescendantBindings) {
throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
}
bindingHandlerThatControlsDescendantBindings = bindingKey;
}
});
}
});
// Run update in its own computed wrapper
if (typeof handlerUpdateFn === 'function') {
dependentObservable(
() => handlerUpdateFn(node, getValueAccessor(bindingKey), allBindings, contextToExtend.$data, contextToExtend),
null,
{disposeWhenNodeIsRemoved: node}
);
}
} catch (ex) {
ex.message = `Unable to process binding "${bindingKey}: ${bindings[bindingKey]}"\nMessage: + ${ex.message}`;
throw ex;
}
}

return {
Expand Down

0 comments on commit 0b81b87

Please sign in to comment.