Skip to content

Commit

Permalink
fix The LiveNodeListWrapper imitates wrongly a native behavior (close
Browse files Browse the repository at this point in the history
#1376) (#1738)

* fix `The LiveNodeListWrapper imitates wrongly a native behavior` (close #1376)

* fix remark

* fix remarks

* fix minor remark

* renaming
  • Loading branch information
LavrovArtem authored and miherlosev committed Aug 27, 2018
1 parent bd9eb28 commit 5124e3c
Show file tree
Hide file tree
Showing 10 changed files with 626 additions and 475 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class DOMMutationTracker {
}

getVersion (tagName) {
if (this._mutations[tagName])
if (tagName in this._mutations)
return this._mutations[tagName].value; // eslint-disable-line no-restricted-properties

return -Infinity;
Expand Down
25 changes: 0 additions & 25 deletions src/client/sandbox/node/live-node-list/factory.js

This file was deleted.

157 changes: 157 additions & 0 deletions src/client/sandbox/node/live-node-list/html-collection-wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import DOMMutationTracker from './dom-mutation-tracker';
import nativeMethods from '../../native-methods';
import { isShadowUIElement } from '../../../utils/dom';

// NOTE: Tags from https://www.w3schools.com/tags/att_name.asp
const ELEMENTS_WITH_NAME_ATTRIBUTE = ['button', 'fieldset', 'form', 'iframe',
'input', 'map', 'meta', 'object', 'output', 'param', 'select', 'textarea'];
const COLLECTION_PROTO_GETTERS_RESERVE = 10;
let collectionProtoGettersCount = 0;

export default function HTMLCollectionWrapper (collection, tagName) {
tagName = tagName.toLowerCase();

nativeMethods.objectDefineProperties.call(Object, this, {
_collection: { value: collection },
_filteredCollection: { value: [] },
_tagName: { value: tagName },
_version: { value: -Infinity, writable: true },
_namedProps: { value: ELEMENTS_WITH_NAME_ATTRIBUTE.indexOf(tagName) !== -1 ? [] : null }
});

this._refreshCollection();
}

HTMLCollectionWrapper.prototype = nativeMethods.objectCreate.call(Object, HTMLCollection.prototype);

HTMLCollectionWrapper.prototype.item = function (index) {
this._refreshCollection();

return this._filteredCollection[index];
};

if (HTMLCollection.prototype.namedItem) {
HTMLCollectionWrapper.prototype.namedItem = function (...args) {
this._refreshCollection();

const namedItem = this._collection.namedItem.apply(this._collection, args);

return namedItem && isShadowUIElement(namedItem) ? null : namedItem;
};
}

nativeMethods.objectDefineProperties.call(Object, HTMLCollectionWrapper.prototype, {
length: {
configurable: true,
enumerable: true,
get: function () {
this._refreshCollection();

return this._filteredCollection.length;
}
},

_refreshCollection: {
value: function () {
if (!DOMMutationTracker.isOutdated(this._tagName, this._version))
return;

const storedFilteredCollectionLength = this._filteredCollection.length;
const currentNamedProps = filterCollection(this);

this._version = DOMMutationTracker.getVersion(this._tagName);

updateCollectionIndexGetters(this, storedFilteredCollectionLength, this._filteredCollection.length);
updateNamedProps(this, this._namedProps, currentNamedProps);
}
}
});

addShadowGetters(COLLECTION_PROTO_GETTERS_RESERVE);

function addShadowGetters (count) {
for (let i = 0; i < count; i++) {
const idx = collectionProtoGettersCount++;

nativeMethods.objectDefineProperty.call(Object, HTMLCollectionWrapper.prototype, idx, {
get: function () {
this.item(idx);
}
});
}
}

function updateCollectionIndexGetters (wrapper, oldLength, currentLength) {
if (oldLength === currentLength)
return;

while (oldLength < currentLength) {
const idx = oldLength++;

nativeMethods.objectDefineProperty.call(Object, wrapper, idx, {
enumerable: true,
configurable: true,
get: () => wrapper.item(idx)
});
}

while (oldLength > currentLength)
delete wrapper[--oldLength];

const maxCollectionLength = collectionProtoGettersCount - COLLECTION_PROTO_GETTERS_RESERVE;

if (currentLength > maxCollectionLength)
addShadowGetters(currentLength - maxCollectionLength);
}

function updateNamedProps (wrapper, oldNamedProps, currentNamedProps) {
if (!currentNamedProps)
return;

for (const oldProp of oldNamedProps) {
if (currentNamedProps.indexOf(oldProp) === -1)
delete wrapper[oldProp];
}

for (const prop of currentNamedProps) {
if (!wrapper._collection[prop])
continue;

nativeMethods.objectDefineProperty.call(Object, wrapper, prop, {
configurable: true,
get: function () {
this._refreshCollection();

return wrapper._collection[prop];
}
});
}
}

function filterCollection (wrapper) {
const nativeCollection = wrapper._collection;
const nativeCollectionLength = nativeMethods.htmlCollectionLengthGetter.call(nativeCollection);
const currentNamedProps = wrapper._namedProps ? [] : null;
const filteredCollection = wrapper._filteredCollection;

filteredCollection.length = 0;

for (let i = 0; i < nativeCollectionLength; i++) {
const el = nativeCollection[i];

if (isShadowUIElement(el))
continue;

filteredCollection.push(el);

if (!currentNamedProps)
continue;

const nameAttr = nativeMethods.getAttribute.call(el, 'name');

if (nameAttr !== null)
currentNamedProps.push(nameAttr);
}

return currentNamedProps;
}
25 changes: 0 additions & 25 deletions src/client/sandbox/node/live-node-list/wrapper-base.js

This file was deleted.

22 changes: 0 additions & 22 deletions src/client/sandbox/node/live-node-list/wrapper-state.js

This file was deleted.

37 changes: 0 additions & 37 deletions src/client/sandbox/node/live-node-list/wrapper.js

This file was deleted.

19 changes: 13 additions & 6 deletions src/client/sandbox/shadow-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import SHADOW_UI_CLASS_NAME from '../../shadow-ui/class-name';
import { get as getStyle, set as setStyle } from '../utils/style';
import { stopPropagation } from '../utils/event';
import { getNativeQuerySelectorAll } from '../utils/query-selector';
import LiveNodeListFactory from './node/live-node-list/factory';
import HTMLCollectionWrapper from './node/live-node-list/html-collection-wrapper';

const IS_NON_STATIC_POSITION_RE = /fixed|relative|absolute/;
const CLASSNAME_RE = /\.((?:\\.|[-\w]|[^\x00-\xa0])+)/g;

const IS_SHADOW_CONTAINER_FLAG = 'hammerhead|shadow-ui|container-flag';
const IS_SHADOW_CONTAINER_COLLECTION_FLAG = 'hammerhead|shadow-ui|container-collection-flag';
const HTML_COLLECTION_WRAPPER = 'hammerhead|shadow-ui|html-collection-wrapper';

export default class ShadowUI extends SandboxBase {
constructor (nodeMutation, messageSandbox, iframeSandbox) {
Expand Down Expand Up @@ -95,12 +96,18 @@ export default class ShadowUI extends SandboxBase {

getElementsByTagName (nativeGetElementsByTagNameFnName) {
return function (...args) {
const nativeResult = nativeMethods[nativeGetElementsByTagNameFnName].apply(this, args);
const nativeCollection = nativeMethods[nativeGetElementsByTagNameFnName].apply(this, args);
const tagName = args[0];

return LiveNodeListFactory.createNodeListForGetElementsByTagNameFn({
nodeList: nativeResult,
tagName: args[0]
});
if (typeof tagName !== 'string')
return nativeCollection;

if (!nativeCollection[HTML_COLLECTION_WRAPPER])
nativeCollection[HTML_COLLECTION_WRAPPER] = new HTMLCollectionWrapper(nativeCollection, tagName);
else
nativeCollection[HTML_COLLECTION_WRAPPER]._refreshCollection();

return nativeCollection[HTML_COLLECTION_WRAPPER];
};
},

Expand Down
19 changes: 8 additions & 11 deletions test/client/data/live-node-list/getElementsByTagName.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<body>
<script>
var domContentLoadedIsRaised = false;
var refreshNodeListCount = 0;
var refreshCollectionCount = 0;
var assertions = [];

document.addEventListener('DOMContentLoaded', function () {
Expand All @@ -20,7 +20,7 @@
hammerhead.get('./utils/destination-location').forceLocation('http://localhost/sessionId/http://origin_iframe_host');
hammerhead.start({ crossDomainProxyPort: 2000 });

var WrapperState = hammerhead.get('./sandbox/node/live-node-list/wrapper-state');
var DOMMutationTrackerProto = Object.getPrototypeOf(hammerhead.get('./sandbox/node/live-node-list/dom-mutation-tracker'));

var testDiv = document.createElement('div');

Expand All @@ -36,16 +36,13 @@
testDiv.appendChild(textarea1);
testDiv.appendChild(textarea2);

var elements = document.getElementsByTagName('textarea');
var storedRefreshNodeListFn = WrapperState.prototype.refreshNodeListIfNecessary;
var elements = document.getElementsByTagName('textarea');
var storedGetVersion = DOMMutationTrackerProto.getVersion;

WrapperState.prototype.refreshNodeListIfNecessary = function () {
var storedFilteredNodeList = this.filteredNodeList;
DOMMutationTrackerProto.getVersion = function () {
refreshCollectionCount++;

storedRefreshNodeListFn.apply(this, arguments);

if (storedFilteredNodeList !== this.filteredNodeList)
refreshNodeListCount++;
return storedGetVersion.apply(this, arguments);
};

assertions.push([elements[0], textarea1, 'elements[0]']);
Expand All @@ -58,7 +55,7 @@
assertions.push([elements[0], textarea1, 'elements[0] after remove textarea2']);
assertions.push([elements[1], void 0, 'elements[1] after remove textarea2']);
assertions.push([elements.length, 1, 'elements.length after remove textarea2']);
assertions.push([refreshNodeListCount, 7, 'count of the refreshNodeList calls']);
assertions.push([refreshCollectionCount, 7, 'count of the refreshNodeList calls']);
assertions.push([domContentLoadedIsRaised, false, 'domContentLoadedIsRaised']);
</script>
</body>
Expand Down
Loading

0 comments on commit 5124e3c

Please sign in to comment.