diff --git a/.autoclave-build.sh b/.autoclave-build.sh new file mode 100755 index 0000000..8bf33e6 --- /dev/null +++ b/.autoclave-build.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# @license +# Copyright (c) 2014 The Polymer Project Authors. All rights reserved. +# This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt +# The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt +# The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt +# Code distributed by Google as part of the polymer project is also +# subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt +npm install + +node_modules/.bin/gulp release + +lasttag=`git tag -l | sort -t. -k1,1n -k2,2n -k3,3n | tail -n 1` +git checkout --detach ${lasttag} +git merge -s ours master --no-commit + +files=(`ls dist | sed -e 's/\/dist//'`) +mv dist/* . + +git add -f "${files[@]}" diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..bb3f440 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +# License + +Everything in this repo is BSD style license unless otherwise specified. + +Copyright (c) 2015 The Polymer Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. +* Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 23dcc3a..49ed2b6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ webcomponents.js ================ +[![Join the chat at https://gitter.im/webcomponents/webcomponentsjs](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/webcomponents/webcomponentsjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + A suite of polyfills supporting the [Web Components](http://webcomponents.org) specs: **Custom Elements**: allows authors to define their own custom tags ([spec](https://w3c.github.io/webcomponents/spec/custom/)). @@ -21,6 +23,28 @@ Pre-built (concatenated & minified) versions of the polyfills are maintained in `webcomponents-lite.js` includes all polyfills except for shadow DOM. +## Browser Support + +Our polyfills are intended to work in the latest versions of evergreen browsers. See below +for our complete browser support matrix: + +| Polyfill | IE10 | IE11+ | Chrome* | Firefox* | Safari 7+* | Chrome Android* | Mobile Safari* | +| ---------- |:----:|:-----:|:-------:|:--------:|:----------:|:---------------:|:--------------:| +| Custom Elements | ~ | ✓ | ✓ | ✓ | ✓ | ✓| ✓ | +| HTML Imports | ~ | ✓ | ✓ | ✓ | ✓| ✓| ✓ | +| Shadow DOM | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Templates | ✓ | ✓ | ✓ | ✓| ✓ | ✓ | ✓ | + + +*Indicates the current version of the browser + +~Indicates support may be flaky. If using Custom Elements or HTML Imports with Shadow DOM, +you will get the non-flaky Mutation Observer polyfill that Shadow DOM includes. + +The polyfills may work in older browsers, however require additional polyfills (such as classList) +to be used. We cannot guarantee support for browsers outside of our compatibility matrix. + + ### Manually Building If you wish to build the polyfills yourself, you'll need `node` and `gulp` on your system: @@ -41,4 +65,9 @@ The builds will be placed into the `dist/` directory. See the [contributing guide](CONTRIBUTING.md) +## License + +Everything in this repository is BSD style license unless otherwise specified. + +Copyright (c) 2015 The Polymer Authors. All rights reserved. diff --git a/bower.json b/bower.json index a689f1f..d874884 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "webcomponentsjs", "main": "webcomponents.js", - "version": "0.5.3", + "version": "0.5.5", "homepage": "http://webcomponents.org", "authors": [ "The Polymer Authors" diff --git a/package.json b/package.json index d8fce91..52fdd0e 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "webcomponents.js", - "version": "0.5.3", + "version": "0.5.5", "description": "webcomponents.js", - "main": "gulpfile.js", + "main": "webcomponents.js", "directories": { "test": "tests" }, diff --git a/src/CustomElements/CustomElements.js b/src/CustomElements/CustomElements.js index 8ef94e6..7b770b4 100644 --- a/src/CustomElements/CustomElements.js +++ b/src/CustomElements/CustomElements.js @@ -28,7 +28,6 @@ var file = 'CustomElements.js'; var modules = [ '../WeakMap/WeakMap.js', - '../MutationObserver/MutationObserver.js', 'base.js', 'traverse.js', 'observe.js', diff --git a/src/CustomElements/build.json b/src/CustomElements/build.json index f8407cd..1723a0a 100644 --- a/src/CustomElements/build.json +++ b/src/CustomElements/build.json @@ -1,6 +1,5 @@ [ "../WeakMap/WeakMap.js", - "../MutationObserver/MutationObserver.js", "base.js", "traverse.js", "observe.js", diff --git a/src/CustomElements/observe.js b/src/CustomElements/observe.js index 23c3146..6a3f3b2 100644 --- a/src/CustomElements/observe.js +++ b/src/CustomElements/observe.js @@ -289,13 +289,17 @@ function upgradeDocumentTree(doc) { } -// ensure that all ShadowRoots watch for CustomElements. +// Patch `createShadowRoot()` if Shadow DOM is available, otherwise leave +// undefined to aid feature detection of Shadow DOM. var originalCreateShadowRoot = Element.prototype.createShadowRoot; -Element.prototype.createShadowRoot = function() { - var root = originalCreateShadowRoot.call(this); - CustomElements.watchShadow(this); - return root; -}; +if (originalCreateShadowRoot) { + // ensure that all ShadowRoots watch for CustomElements. + Element.prototype.createShadowRoot = function() { + var root = originalCreateShadowRoot.call(this); + CustomElements.watchShadow(this); + return root; + }; +} // exports scope.watchShadow = watchShadow; diff --git a/src/HTMLImports/HTMLImports.js b/src/HTMLImports/HTMLImports.js index 2b76f5d..bf3f12d 100644 --- a/src/HTMLImports/HTMLImports.js +++ b/src/HTMLImports/HTMLImports.js @@ -27,7 +27,6 @@ var file = 'HTMLImports.js'; var modules = [ '../WeakMap/WeakMap.js', - '../MutationObserver/MutationObserver.js', 'base.js', 'module.js', 'path.js', diff --git a/src/HTMLImports/base.js b/src/HTMLImports/base.js index f12f8f8..d86d298 100644 --- a/src/HTMLImports/base.js +++ b/src/HTMLImports/base.js @@ -115,24 +115,35 @@ function markTargetLoaded(event) { // call when we ensure all imports have loaded function watchImportsLoad(callback, doc) { var imports = doc.querySelectorAll('link[rel=import]'); - var loaded = 0, l = imports.length; - function checkDone(d) { - if ((loaded == l) && callback) { - callback(); + var parsedCount = 0, importCount = imports.length, newImports = [], errorImports = []; + function checkDone() { + if (parsedCount == importCount && callback) { + callback({ + allImports: imports, + loadedImports: newImports, + errorImports: errorImports + }); } } function loadedImport(e) { markTargetLoaded(e); - loaded++; + newImports.push(this); + parsedCount++; checkDone(); } - if (l) { - for (var i=0, imp; (i 0x20 && + unicode < 0x7F && + // " # < > ? ` + [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1 + ) { + return c; + } + return encodeURIComponent(c); + } + + function percentEscapeQuery(c) { + // XXX This actually needs to encode c using encoding and then + // convert the bytes one-by-one. + + var unicode = c.charCodeAt(0); + if (unicode > 0x20 && + unicode < 0x7F && + // " # < > ` (do not escape '?') + [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1 + ) { + return c; + } + return encodeURIComponent(c); + } + + var EOF = undefined, + ALPHA = /[a-zA-Z]/, + ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/; + + function parse(input, stateOverride, base) { + function err(message) { + errors.push(message) + } + + var state = stateOverride || 'scheme start', + cursor = 0, + buffer = '', + seenAt = false, + seenBracket = false, + errors = []; + + loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid) { + var c = input[cursor]; + switch (state) { + case 'scheme start': + if (c && ALPHA.test(c)) { + buffer += c.toLowerCase(); // ASCII-safe + state = 'scheme'; + } else if (!stateOverride) { + buffer = ''; + state = 'no scheme'; + continue; + } else { + err('Invalid scheme.'); + break loop; + } + break; + + case 'scheme': + if (c && ALPHANUMERIC.test(c)) { + buffer += c.toLowerCase(); // ASCII-safe + } else if (':' == c) { + this._scheme = buffer; + buffer = ''; + if (stateOverride) { + break loop; + } + if (isRelativeScheme(this._scheme)) { + this._isRelative = true; + } + if ('file' == this._scheme) { + state = 'relative'; + } else if (this._isRelative && base && base._scheme == this._scheme) { + state = 'relative or authority'; + } else if (this._isRelative) { + state = 'authority first slash'; + } else { + state = 'scheme data'; + } + } else if (!stateOverride) { + buffer = ''; + cursor = 0; + state = 'no scheme'; + continue; + } else if (EOF == c) { + break loop; + } else { + err('Code point not allowed in scheme: ' + c) + break loop; + } + break; + + case 'scheme data': + if ('?' == c) { + query = '?'; + state = 'query'; + } else if ('#' == c) { + this._fragment = '#'; + state = 'fragment'; + } else { + // XXX error handling + if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { + this._schemeData += percentEscape(c); + } + } + break; + + case 'no scheme': + if (!base || !(isRelativeScheme(base._scheme))) { + err('Missing scheme.'); + invalid.call(this); + } else { + state = 'relative'; + continue; + } + break; + + case 'relative or authority': + if ('/' == c && '/' == input[cursor+1]) { + state = 'authority ignore slashes'; + } else { + err('Expected /, got: ' + c); + state = 'relative'; + continue + } + break; + + case 'relative': + this._isRelative = true; + if ('file' != this._scheme) + this._scheme = base._scheme; + if (EOF == c) { + this._host = base._host; + this._port = base._port; + this._path = base._path.slice(); + this._query = base._query; + break loop; + } else if ('/' == c || '\\' == c) { + if ('\\' == c) + err('\\ is an invalid code point.'); + state = 'relative slash'; + } else if ('?' == c) { + this._host = base._host; + this._port = base._port; + this._path = base._path.slice(); + this._query = '?'; + state = 'query'; + } else if ('#' == c) { + this._host = base._host; + this._port = base._port; + this._path = base._path.slice(); + this._query = base._query; + this._fragment = '#'; + state = 'fragment'; + } else { + var nextC = input[cursor+1] + var nextNextC = input[cursor+2] + if ( + 'file' != this._scheme || !ALPHA.test(c) || + (nextC != ':' && nextC != '|') || + (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?' != nextNextC && '#' != nextNextC)) { + this._host = base._host; + this._port = base._port; + this._path = base._path.slice(); + this._path.pop(); + } + state = 'relative path'; + continue; + } + break; + + case 'relative slash': + if ('/' == c || '\\' == c) { + if ('\\' == c) { + err('\\ is an invalid code point.'); + } + if ('file' == this._scheme) { + state = 'file host'; + } else { + state = 'authority ignore slashes'; + } + } else { + if ('file' != this._scheme) { + this._host = base._host; + this._port = base._port; + } + state = 'relative path'; + continue; + } + break; + + case 'authority first slash': + if ('/' == c) { + state = 'authority second slash'; + } else { + err("Expected '/', got: " + c); + state = 'authority ignore slashes'; + continue; + } + break; + + case 'authority second slash': + state = 'authority ignore slashes'; + if ('/' != c) { + err("Expected '/', got: " + c); + continue; + } + break; + + case 'authority ignore slashes': + if ('/' != c && '\\' != c) { + state = 'authority'; + continue; + } else { + err('Expected authority, got: ' + c); + } + break; + + case 'authority': + if ('@' == c) { + if (seenAt) { + err('@ already seen.'); + buffer += '%40'; + } + seenAt = true; + for (var i = 0; i < buffer.length; i++) { + var cp = buffer[i]; + if ('\t' == cp || '\n' == cp || '\r' == cp) { + err('Invalid whitespace in authority.'); + continue; + } + // XXX check URL code points + if (':' == cp && null === this._password) { + this._password = ''; + continue; + } + var tempC = percentEscape(cp); + (null !== this._password) ? this._password += tempC : this._username += tempC; + } + buffer = ''; + } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { + cursor -= buffer.length; + buffer = ''; + state = 'host'; + continue; + } else { + buffer += c; + } + break; + + case 'file host': + if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { + if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':' || buffer[1] == '|')) { + state = 'relative path'; + } else if (buffer.length == 0) { + state = 'relative path start'; + } else { + this._host = IDNAToASCII.call(this, buffer); + buffer = ''; + state = 'relative path start'; + } + continue; + } else if ('\t' == c || '\n' == c || '\r' == c) { + err('Invalid whitespace in file host.'); + } else { + buffer += c; + } + break; + + case 'host': + case 'hostname': + if (':' == c && !seenBracket) { + // XXX host parsing + this._host = IDNAToASCII.call(this, buffer); + buffer = ''; + state = 'port'; + if ('hostname' == stateOverride) { + break loop; + } + } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { + this._host = IDNAToASCII.call(this, buffer); + buffer = ''; + state = 'relative path start'; + if (stateOverride) { + break loop; + } + continue; + } else if ('\t' != c && '\n' != c && '\r' != c) { + if ('[' == c) { + seenBracket = true; + } else if (']' == c) { + seenBracket = false; + } + buffer += c; + } else { + err('Invalid code point in host/hostname: ' + c); + } + break; + + case 'port': + if (/[0-9]/.test(c)) { + buffer += c; + } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c || stateOverride) { + if ('' != buffer) { + var temp = parseInt(buffer, 10); + if (temp != relative[this._scheme]) { + this._port = temp + ''; + } + buffer = ''; + } + if (stateOverride) { + break loop; + } + state = 'relative path start'; + continue; + } else if ('\t' == c || '\n' == c || '\r' == c) { + err('Invalid code point in port: ' + c); + } else { + invalid.call(this); + } + break; + + case 'relative path start': + if ('\\' == c) + err("'\\' not allowed in path."); + state = 'relative path'; + if ('/' != c && '\\' != c) { + continue; + } + break; + + case 'relative path': + if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c || '#' == c))) { + if ('\\' == c) { + err('\\ not allowed in relative path.'); + } + var tmp; + if (tmp = relativePathDotMapping[buffer.toLowerCase()]) { + buffer = tmp; + } + if ('..' == buffer) { + this._path.pop(); + if ('/' != c && '\\' != c) { + this._path.push(''); + } + } else if ('.' == buffer && '/' != c && '\\' != c) { + this._path.push(''); + } else if ('.' != buffer) { + if ('file' == this._scheme && this._path.length == 0 && buffer.length == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') { + buffer = buffer[0] + ':'; + } + this._path.push(buffer); + } + buffer = ''; + if ('?' == c) { + this._query = '?'; + state = 'query'; + } else if ('#' == c) { + this._fragment = '#'; + state = 'fragment'; + } + } else if ('\t' != c && '\n' != c && '\r' != c) { + buffer += percentEscape(c); + } + break; + + case 'query': + if (!stateOverride && '#' == c) { + this._fragment = '#'; + state = 'fragment'; + } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { + this._query += percentEscapeQuery(c); + } + break; + + case 'fragment': + if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { + this._fragment += c; + } + break; + } + + cursor++; + } + } + + function clear() { + this._scheme = ''; + this._schemeData = ''; + this._username = ''; + this._password = null; + this._host = ''; + this._port = ''; + this._path = []; + this._query = ''; + this._fragment = ''; + this._isInvalid = false; + this._isRelative = false; + } + + // Does not process domain names or IP addresses. + // Does not handle encoding for the query parameter. + function jURL(url, base /* , encoding */) { + if (base !== undefined && !(base instanceof jURL)) + base = new jURL(String(base)); + + this._url = url; + clear.call(this); + + var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ''); + // encoding = encoding || 'utf-8' + + parse.call(this, input, null, base); + } + + jURL.prototype = { + get href() { + if (this._isInvalid) + return this._url; + + var authority = ''; + if ('' != this._username || null != this._password) { + authority = this._username + + (null != this._password ? ':' + this._password : '') + '@'; + } + + return this.protocol + + (this._isRelative ? '//' + authority + this.host : '') + + this.pathname + this._query + this._fragment; + }, + set href(href) { + clear.call(this); + parse.call(this, href); + }, + + get protocol() { + return this._scheme + ':'; + }, + set protocol(protocol) { + if (this._isInvalid) + return; + parse.call(this, protocol + ':', 'scheme start'); + }, + + get host() { + return this._isInvalid ? '' : this._port ? + this._host + ':' + this._port : this._host; + }, + set host(host) { + if (this._isInvalid || !this._isRelative) + return; + parse.call(this, host, 'host'); + }, + + get hostname() { + return this._host; + }, + set hostname(hostname) { + if (this._isInvalid || !this._isRelative) + return; + parse.call(this, hostname, 'hostname'); + }, + + get port() { + return this._port; + }, + set port(port) { + if (this._isInvalid || !this._isRelative) + return; + parse.call(this, port, 'port'); + }, + + get pathname() { + return this._isInvalid ? '' : this._isRelative ? + '/' + this._path.join('/') : this._schemeData; + }, + set pathname(pathname) { + if (this._isInvalid || !this._isRelative) + return; + this._path = []; + parse.call(this, pathname, 'relative path start'); + }, + + get search() { + return this._isInvalid || !this._query || '?' == this._query ? + '' : this._query; + }, + set search(search) { + if (this._isInvalid || !this._isRelative) + return; + this._query = '?'; + if ('?' == search[0]) + search = search.slice(1); + parse.call(this, search, 'query'); + }, + + get hash() { + return this._isInvalid || !this._fragment || '#' == this._fragment ? + '' : this._fragment; + }, + set hash(hash) { + if (this._isInvalid) + return; + this._fragment = '#'; + if ('#' == hash[0]) + hash = hash.slice(1); + parse.call(this, hash, 'fragment'); + }, + + get origin() { + var host; + if (this._isInvalid || !this._scheme) { + return ''; + } + // javascript: Gecko returns String(""), WebKit/Blink String("null") + // Gecko throws error for "data://" + // data: Gecko returns "", Blink returns "data://", WebKit returns "null" + // Gecko returns String("") for file: mailto: + // WebKit/Blink returns String("SCHEME://") for file: mailto: + switch (this._scheme) { + case 'data': + case 'file': + case 'javascript': + case 'mailto': + return 'null'; + } + host = this.host; + if (!host) { + return ''; + } + return this._scheme + '://' + host; + } + }; + + // Copy over the static methods + var OriginalURL = scope.URL; + if (OriginalURL) { + jURL.createObjectURL = function(blob) { + // IE extension allows a second optional options argument. + // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx + return OriginalURL.createObjectURL.apply(OriginalURL, arguments); + }; + jURL.revokeObjectURL = function(url) { + OriginalURL.revokeObjectURL(url); + }; + } + + scope.URL = jURL; + +})(this); diff --git a/src/WebComponents/build-lite.json b/src/WebComponents/build-lite.json index dead003..257a488 100644 --- a/src/WebComponents/build-lite.json +++ b/src/WebComponents/build-lite.json @@ -1,5 +1,6 @@ [ "build/boot.js", + "../MutationObserver/MutationObserver.js", "../HTMLImports/build.json", "../CustomElements/build.json", "../Template/Template.js", diff --git a/tests/HTMLImports/html/dynamic-all-imports-detail.html b/tests/HTMLImports/html/dynamic-all-imports-detail.html new file mode 100644 index 0000000..8bdfdcf --- /dev/null +++ b/tests/HTMLImports/html/dynamic-all-imports-detail.html @@ -0,0 +1,49 @@ + + + + + HTML Imports Dynamic + + + + + + + + + diff --git a/tests/HTMLImports/html/dynamic-errors-detail.html b/tests/HTMLImports/html/dynamic-errors-detail.html new file mode 100644 index 0000000..c10ce16 --- /dev/null +++ b/tests/HTMLImports/html/dynamic-errors-detail.html @@ -0,0 +1,50 @@ + + + + + HTML Imports Dynamic + + + + + + + + + diff --git a/tests/HTMLImports/html/dynamic-loaded-detail.html b/tests/HTMLImports/html/dynamic-loaded-detail.html new file mode 100644 index 0000000..6d963aa --- /dev/null +++ b/tests/HTMLImports/html/dynamic-loaded-detail.html @@ -0,0 +1,50 @@ + + + + + HTML Imports Dynamic + + + + + + + + + diff --git a/tests/HTMLImports/tests.js b/tests/HTMLImports/tests.js index 83f0879..ee82f91 100644 --- a/tests/HTMLImports/tests.js +++ b/tests/HTMLImports/tests.js @@ -21,6 +21,9 @@ htmlSuite('HTMLImports', function() { htmlTest('html/currentScript.html'); htmlTest('html/dedupe.html'); htmlTest('html/dynamic.html'); + htmlTest('html/dynamic-all-imports-detail.html'); + htmlTest('html/dynamic-errors-detail.html'); + htmlTest('html/dynamic-loaded-detail.html'); htmlTest('html/csp.html'); htmlTest('html/customevent-detail.html'); htmlTest('html/encoding.html'); diff --git a/tests/MutationObservers/childList.js b/tests/MutationObservers/childList.js index a54f915..ab24c37 100644 --- a/tests/MutationObservers/childList.js +++ b/tests/MutationObservers/childList.js @@ -378,4 +378,25 @@ suite('JsMutationObserver childList', function() { }); }); + test('Append child in child', function() { + var div = testDiv.appendChild(document.createElement('div')); + + var observer = new JsMutationObserver(function() {}); + observer.observe(div, { + childList: true, + subtree: true + }); + var div2 = document.createElement('div') + var div3 = div2.appendChild(document.createElement('div')); + div.appendChild(div2); + var records = observer.takeRecords(); + + if(records.length == 1) { + assert.strictEqual(records[0].target, div); + assert.strictEqual(records[0].addedNodes[0].firstChild, div3); + } else { + assert.strictEqual(records[0].target, div); + assert.strictEqual(records[1].target, div2); + } + }); }); diff --git a/tests/MutationObservers/runner.html b/tests/MutationObservers/runner.html index ea8c3bc..d5b0406 100644 --- a/tests/MutationObservers/runner.html +++ b/tests/MutationObservers/runner.html @@ -17,6 +17,7 @@ +