From ecb934d4048d268dcea003dcb71e0d45a1ef9bb5 Mon Sep 17 00:00:00 2001 From: Herman Lee Date: Wed, 11 Mar 2015 17:37:03 +0800 Subject: [PATCH 1/9] Add URL toString() method to return synonym for href. (according to spec) Current build: var u = new URL('b', 'http://a'); console.log( u.toString() ); // "[object Object]"" After patch: var u = new URL('b', 'http://a'); console.log( u.toString() ); // "http://a/b" // currently I cannot find URL related test on tests folder, should I write some tests for this patch? --- src/URL/URL.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/URL/URL.js b/src/URL/URL.js index e53f5577d..b0079a68a 100644 --- a/src/URL/URL.js +++ b/src/URL/URL.js @@ -467,6 +467,9 @@ } jURL.prototype = { + toString: function() { + return this.href; + }, get href() { if (this._isInvalid) return this._url; From 94a3292748b097173cbbc1ba9f9da71e2408c599 Mon Sep 17 00:00:00 2001 From: John Messerly Date: Wed, 25 Mar 2015 10:32:11 -0700 Subject: [PATCH 2/9] fix #256 -- DOM objects with numeric properties --- src/ShadowDOM/wrappers.js | 3 ++- tests/ShadowDOM/js/wrappers.js | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/ShadowDOM/wrappers.js b/src/ShadowDOM/wrappers.js index b996117aa..53dd79ce3 100644 --- a/src/ShadowDOM/wrappers.js +++ b/src/ShadowDOM/wrappers.js @@ -150,7 +150,7 @@ window.ShadowDOMPolyfill = {}; } function isIdentifierName(name) { - return /^\w[a-zA-Z_0-9]*$/.test(name); + return /^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name); } // The name of the implementation property is intentionally hard to @@ -426,6 +426,7 @@ window.ShadowDOMPolyfill = {}; scope.defineGetter = defineGetter; scope.defineWrapGetter = defineWrapGetter; scope.forwardMethodsToWrapper = forwardMethodsToWrapper; + scope.isIdentifierName = isIdentifierName; scope.isWrapper = isWrapper; scope.isWrapperFor = isWrapperFor; scope.mixin = mixin; diff --git a/tests/ShadowDOM/js/wrappers.js b/tests/ShadowDOM/js/wrappers.js index 0b1307e5b..0c1d2b0f1 100644 --- a/tests/ShadowDOM/js/wrappers.js +++ b/tests/ShadowDOM/js/wrappers.js @@ -114,4 +114,29 @@ suite('Wrapper creation', function() { }); }); + test('isIdentifierName', function() { + var isIdentifierName = ShadowDOMPolyfill.isIdentifierName; + // Not identiifers: + assert.isFalse(isIdentifierName('123')); + assert.isFalse(isIdentifierName('%123')); + assert.isFalse(isIdentifierName('-123')); + + // Identifiers: + assert.isTrue(isIdentifierName('_123')); + assert.isTrue(isIdentifierName('$123')); + assert.isTrue(isIdentifierName('abC')); + assert.isTrue(isIdentifierName('abc$123')); + }); + + test('Integer property names', function() { + // Create a fake "native" DOM object to test wrapping a numeric property. + function TestNative() {} + TestNative.prototype['123'] = function() { return 42; }; + function TestWrapper() { + ShadowDOMPolyfill.setWrapper(new TestNative(), this); + } + ShadowDOMPolyfill.registerWrapper(TestNative, TestWrapper); + var wrapper = new TestWrapper(); + assert.equal(wrapper['123'](), 42); + }); }); From 209cf3d6cd6ed7987ac9504d807eb936118f2e29 Mon Sep 17 00:00:00 2001 From: Emad Eid Date: Wed, 1 Apr 2015 21:34:28 -0400 Subject: [PATCH 3/9] fix issues with range and elements with shadow dom. Range needs to work properly before thinking about supporting contentEditable and the editing api --- src/ShadowDOM/ShadowDOM.js | 2 +- src/ShadowDOM/build.json | 2 +- src/ShadowDOM/wrappers/Range.js | 52 ++- src/ShadowDOM/wrappers/Selection.js | 2 +- tests/ShadowDOM/js/Range.js | 543 +++++++++++++++++++++++++--- tests/ShadowDOM/js/Selection.js | 1 + 6 files changed, 549 insertions(+), 53 deletions(-) diff --git a/src/ShadowDOM/ShadowDOM.js b/src/ShadowDOM/ShadowDOM.js index dd7ffc564..63ff20966 100644 --- a/src/ShadowDOM/ShadowDOM.js +++ b/src/ShadowDOM/ShadowDOM.js @@ -59,9 +59,9 @@ Array.prototype.forEach.call(document.querySelectorAll('script[src]'), function( 'wrappers/SVGElementInstance.js', 'wrappers/CanvasRenderingContext2D.js', 'wrappers/WebGLRenderingContext.js', - 'wrappers/Range.js', 'wrappers/generic.js', 'wrappers/ShadowRoot.js', + 'wrappers/Range.js', 'ShadowRenderer.js', 'wrappers/elements-with-form-property.js', 'wrappers/Selection.js', diff --git a/src/ShadowDOM/build.json b/src/ShadowDOM/build.json index 7c53ce2a2..f0e67675e 100644 --- a/src/ShadowDOM/build.json +++ b/src/ShadowDOM/build.json @@ -36,9 +36,9 @@ "wrappers/SVGElementInstance.js", "wrappers/CanvasRenderingContext2D.js", "wrappers/WebGLRenderingContext.js", - "wrappers/Range.js", "wrappers/generic.js", "wrappers/ShadowRoot.js", + "wrappers/Range.js", "ShadowRenderer.js", "wrappers/elements-with-form-property.js", "wrappers/Selection.js", diff --git a/src/ShadowDOM/wrappers/Range.js b/src/ShadowDOM/wrappers/Range.js index cf9a63b58..406ffbfc5 100644 --- a/src/ShadowDOM/wrappers/Range.js +++ b/src/ShadowDOM/wrappers/Range.js @@ -20,23 +20,69 @@ var OriginalRange = window.Range; + var ShadowRoot = scope.wrappers.ShadowRoot; + + function isElement(node){ + return node && node.nodeType == Node.ELEMENT_NODE + } + + function getHost(node){ + while(node && node.parentNode){ + if (node.parentNode && node.parentNode instanceof ShadowRoot){ + return node.parentNode.host; + } + node=node.parentNode; + } + } + + function hostNodeToShadowNode(refNode,offset){ + if (refNode.shadowRoot){ + // Note: if the refNode is an element, then selecting a range with and offset + // equal to refNode.childNodes.length+1 is valid. That is why calling Math.min + // is necessary to make sure we select valid children. + var child = refNode.childNodes[Math.min(refNode.childNodes.length-1, offset)]; + if (child){ + var insertionPoint = ShadowDOMPolyfill.getDestinationInsertionPoints(child); + if (insertionPoint.length>0){ + var parentNode = insertionPoint[0].parentNode; + if (isElement(parentNode)){ + refNode=parentNode; + } + } + } + } + return refNode; + } + + function shadowNodeToHostNode(rangeNodeFieldName){ + var node = wrap(unsafeUnwrap(this)[rangeNodeFieldName]); + var host = getHost(node); + if (host){ + return host; + } + return node; + } + function Range(impl) { setWrapper(impl, this); } Range.prototype = { get startContainer() { - return wrap(unsafeUnwrap(this).startContainer); + // Never return a node in the shadow dom. + return shadowNodeToHostNode.call(this,"startContainer"); }, get endContainer() { - return wrap(unsafeUnwrap(this).endContainer); + return shadowNodeToHostNode.call(this,"endContainer"); }, get commonAncestorContainer() { - return wrap(unsafeUnwrap(this).commonAncestorContainer); + return shadowNodeToHostNode.call(this,"commonAncestorContainer"); }, setStart: function(refNode,offset) { + refNode=hostNodeToShadowNode(refNode,offset); unsafeUnwrap(this).setStart(unwrapIfNeeded(refNode), offset); }, setEnd: function(refNode,offset) { + refNode=hostNodeToShadowNode(refNode,offset); unsafeUnwrap(this).setEnd(unwrapIfNeeded(refNode), offset); }, setStartBefore: function(refNode) { diff --git a/src/ShadowDOM/wrappers/Selection.js b/src/ShadowDOM/wrappers/Selection.js index 3714f8d8f..9015b6e36 100644 --- a/src/ShadowDOM/wrappers/Selection.js +++ b/src/ShadowDOM/wrappers/Selection.js @@ -31,7 +31,7 @@ return wrap(unsafeUnwrap(this).focusNode); }, addRange: function(range) { - unsafeUnwrap(this).addRange(unwrap(range)); + unsafeUnwrap(this).addRange(unwrapIfNeeded(range)); }, collapse: function(node, index) { unsafeUnwrap(this).collapse(unwrapIfNeeded(node), index); diff --git a/tests/ShadowDOM/js/Range.js b/tests/ShadowDOM/js/Range.js index c180ffa57..492b64c34 100644 --- a/tests/ShadowDOM/js/Range.js +++ b/tests/ShadowDOM/js/Range.js @@ -11,76 +11,525 @@ suite('Range', function() { var wrap = ShadowDOMPolyfill.wrap; + var unwrap = ShadowDOMPolyfill.unwrap; + var wrapIfNeeded = ShadowDOMPolyfill.wrapIfNeeded; + var isNativeShadowDomSupported; - var div; + var hosts; + var customElementPrefix = "range-custom-element-"; + var nativeCustomElementPrefix = "range-native-element-"; + var customElementIndex=0; + var nativeCustomElementIndex=0; - teardown(function() { - if (div && div.parentNode) - div.parentNode.removeChild(div); - div = undefined; + setup(function() { + isNativeShadowDomSupported = !!unwrap(document.createElement('div')).createShadowRoot; }); - test('instanceof', function() { - var range = document.createRange(); - assert.instanceOf(range, Range); + function removeHosts(){ + if (hosts){ + hosts.forEach(function(host){ + if (host && host.parentNode){ + host.parentNode.removeChild(wrapIfNeeded(host)); + } + }); + hosts = undefined; + } + } - var range2 = wrap(document).createRange(); - assert.instanceOf(range2, Range); - }); + function getNewCustomElementType(){ + return customElementPrefix+customElementIndex++; + } + + function getNewNativeCustomElementType(){ + return nativeCustomElementPrefix+nativeCustomElementIndex++; + } + + function createCustomElement(name,shadowDomContentsArray,native){ + + var prototype = Object.create(HTMLElement.prototype); + prototype.createdCallback = function() { + var element = this; + if (native){ + element = unwrap(this); + } + createShadowDom(element,shadowDomContentsArray); + }; + + return document.registerElement(name, {prototype:prototype}); + } - test('constructor', function() { + // If the host has native shadow dom then we need to return native + // range. Native range is just a polyfill Range unwrapped. + function createRangeForHost(host){ var range = document.createRange(); - assert.equal(Range, range.constructor); - }); - test('createContextualFragment', function() { - // IE9 does not support createContextualFragment. - if (!Range.prototype.createContextualFragment) + // If we are dealing with native shadow dom, expose the range object + // as a native range object by just unwrapping it. + //noinspection JSUnresolvedVariable + if (hasNativeShadowRoot(host)){ + range = unwrap(range); + } + + return range; + } + + function hasNativeShadowRoot(node){ + return node && node.shadowRoot && !(node.shadowRoot instanceof ShadowRoot); + } + + function createCustomElementWithPolyfillShadowDom(shadowDomContentsArray) { + var customElementType = getNewCustomElementType(); + createCustomElement(customElementType, shadowDomContentsArray); + return document.createElement(customElementType); + } + + function createStandardElementWithPolyfillShadowDom(shadowDomContentsArray, + elementType){ + var element = document.createElement(elementType); + return createShadowDom(element,shadowDomContentsArray); + } + + function createHostWithPolyfillShadowDom(shadowDomContentsArray, elementType){ + if (!elementType) { + return createCustomElementWithPolyfillShadowDom(shadowDomContentsArray); + } else { + return createStandardElementWithPolyfillShadowDom(shadowDomContentsArray, + elementType); + } + } + + function createShadowDom(element,shadowDomContentsArray){ + shadowDomContentsArray.forEach(function(shadowDomContent){ + element.createShadowRoot().innerHTML=shadowDomContent; + }); + return element; + } + + function createStandardElementWithNativeShadowDom(shadowDomContentsArray, + elementType){ + var element = document.createElement(elementType); + return createShadowDom(unwrap(element),shadowDomContentsArray); + } + + function createCustomElementWithNativeShadowDom(shadowDomContentsArray){ + var element; + var nativeElementType = getNewNativeCustomElementType(); + createCustomElement(nativeElementType, shadowDomContentsArray, true); + element = document.createElement(nativeElementType); + element = unwrap(element); + assert.isNotNull(element.shadowRoot); + } + + function createHostWithNativeShadowDom(shadowDomContentsArray, elementType){ + + if (!isNativeShadowDomSupported){ return; + } - var range = document.createRange(); - var container = document.body || document.head; + if (!elementType){ + return createCustomElementWithNativeShadowDom(shadowDomContentsArray, + elementType); + }else{ + return createStandardElementWithNativeShadowDom(shadowDomContentsArray, + elementType); + } + } + + // Create hosts with polyfill shadow dom and native shadow dom + // if available. The two hosts then will be tested by setting + // the innerHTML of those hosts and using the polyfill range or + // the native range. The results should be the same in both cases. + function createHostsWithShadowDom(shadowDomContentsArray, elementType){ + + var hostWithPolyFillShadowDom = + createHostWithPolyfillShadowDom(shadowDomContentsArray, elementType); + var hostWithNativeShadowDom = + createHostWithNativeShadowDom(shadowDomContentsArray, elementType); + + var hosts = []; + assert.isObject(hostWithPolyFillShadowDom); + + hosts.push(hostWithPolyFillShadowDom); + if (hostWithNativeShadowDom){ + hosts.push(hostWithNativeShadowDom); + } + + return hosts; + } + + // This function sets the innerHTML for some host element. The host could + // have native shadow dom or polyfill shadow dom. Then we start selecting + // the range based on the set innerHTML and the range has to work + // regardless of the structure of the shadow dom. + function testRangeWith3SpansHTML(host){ + + host.innerHTML = "OneTwoThree"; + + assert.isNotNull(host.shadowRoot); + + // Force rendering for the host with the polyfill shadow dom. + // Of course the host with native shadow dom does not need it. + host.offsetWidth; + + var range = createRangeForHost(host); + + // We are using the polyfill selection for native and polyfill ranges. + // It has no impact on the tests results. + var selection = document.getSelection(); + if (selection.rangeCount>0){ + selection.removeAllRanges(); + } + + // We do not really have to add the range to the selection. + // It provides visual feedback of the range while we are debugging. + + range.setStart(host,0); + range.setEnd(host,2); + selection.addRange(range); + + assert.isTrue(range.startContainer === host); + assert.isTrue(range.endContainer === host); + assert.isTrue(range.commonAncestorContainer === host); + assert.isTrue(range.toString() === "OneTwo"); + + range.setStart(host,0); + range.setEnd(host,1); + assert.isTrue(range.toString() === "One"); + selection.removeAllRanges(); + selection.addRange(range); + + range.setStart(host,1); + range.setEnd(host,2); + assert.isTrue(range.toString() === "Two"); + selection.removeAllRanges(); + selection.addRange(range); + + range.setStart(host,2); + range.setEnd(host,3); + assert.isTrue(range.toString() === "Three"); + selection.removeAllRanges(); + selection.addRange(range); + + range.setStart(host,0); + range.setEnd(host,3); + assert.isTrue(range.toString() === "OneTwoThree"); + selection.removeAllRanges(); + selection.addRange(range); + + // Make sure we can select without specifying the host + + // Test selecting the spans inside the spans + var span0 = host.childNodes[0]; + var span2 = host.childNodes[2]; + range.setStart(span0, 1); + range.setEnd(span2, 0); + assert.isTrue(range.toString() === "Two"); + selection.removeAllRanges(); + selection.addRange(range); + + // create span0TextNode and span2TextNode for test readability. + // Test selecting the text nodes inside the spans + var span0TextNode = span0.childNodes[0]; + var span2TextNode = span2.childNodes[0]; + range.setStart(span0TextNode,1); + range.setEnd(span2TextNode,1); + selection.removeAllRanges(); + selection.addRange(range); + assert.isTrue(range.toString() === "neTwoT"); + } + + function testRangeWithHosts(hosts){ + hosts.forEach(function(host){ + document.body.appendChild(wrapIfNeeded(host)); + testRangeWith3SpansHTML(host); + }); + } + + suite('Standard elements (no Shadow Dom)',function(){ + var div; + + teardown(function() { + if (div && div.parentNode) + div.parentNode.removeChild(div); + div = undefined; + }); + + test('instanceof', function() { + var range = document.createRange(); + assert.instanceOf(range, Range); + + var range2 = wrap(document).createRange(); + assert.instanceOf(range2, Range); + }); + + test('constructor', function() { + var range = document.createRange(); + assert.equal(Range, range.constructor); + }); + + test('createContextualFragment', function() { + // IE9 does not support createContextualFragment. + if (!Range.prototype.createContextualFragment) + return; + + var range = document.createRange(); + var container = document.body || document.head; - range.selectNode(container); + range.selectNode(container); - var fragment = range.createContextualFragment(''); + var fragment = range.createContextualFragment(''); + + assert.instanceOf(fragment, DocumentFragment); + assert.equal(fragment.firstChild.localName, 'b'); + assert.equal(fragment.childNodes.length, 1); + }); + + test('WebIDL attributes', function() { + var range = document.createRange(); + + assert.isTrue('collapsed' in range); + assert.isFalse(range.hasOwnProperty('collapsed')); + + assert.isTrue('commonAncestorContainer' in range); + assert.isFalse(range.hasOwnProperty('commonAncestorContainer')); + + assert.isTrue('endContainer' in range); + assert.isFalse(range.hasOwnProperty('endContainer')); + + assert.isTrue('endOffset' in range); + assert.isFalse(range.hasOwnProperty('endOffset')); + + assert.isTrue('startContainer' in range); + assert.isFalse(range.hasOwnProperty('startContainer')); + + assert.isTrue('startOffset' in range); + assert.isFalse(range.hasOwnProperty('startOffset')); + }); + + test('toString', function() { + var range = document.createRange(); + div = document.createElement('div'); + document.body.appendChild(div); + div.innerHTML = 'abc'; + var a = div.firstChild; + var b = a.nextSibling; + range.selectNode(b); + assert.equal(range.toString(), 'b'); + }); - assert.instanceOf(fragment, DocumentFragment); - assert.equal(fragment.firstChild.localName, 'b'); - assert.equal(fragment.childNodes.length, 1); }); - test('WebIDL attributes', function() { - var range = document.createRange(); + suite('Standard+Custom elements with Shadow Dom',function(){ + + teardown(function() { + removeHosts(); + }); + + // create a prototype for each test, so we don't get into some + // other issues that has nothing to do with the Range. + test('custom - ', function() { + if (!document.registerElement) + return; + var shadowDomContent = ""; + hosts = createHostsWithShadowDom([shadowDomContent]); + testRangeWithHosts(hosts); + }); - assert.isTrue('collapsed' in range); - assert.isFalse(range.hasOwnProperty('collapsed')); + test('custom - ', function() { + if (!document.registerElement) + return; + var shadowDomContent = ""; + hosts = createHostsWithShadowDom([shadowDomContent]); + testRangeWithHosts(hosts); + }); - assert.isTrue('commonAncestorContainer' in range); - assert.isFalse(range.hasOwnProperty('commonAncestorContainer')); + test("custom - wrapped in a div container", function() { + if (!document.registerElement) + return; + var shadowDomContent = "
"; + hosts = createHostsWithShadowDom([shadowDomContent]); + testRangeWithHosts(hosts); + }); - assert.isTrue('endContainer' in range); - assert.isFalse(range.hasOwnProperty('endContainer')); + test("custom - wrapped in a div container", function() { + if (!document.registerElement) + return; + var shadowDomContent = "
"; + hosts = createHostsWithShadowDom([shadowDomContent]); + testRangeWithHosts(hosts); + }); - assert.isTrue('endOffset' in range); - assert.isFalse(range.hasOwnProperty('endOffset')); + test("custom - wrapped and more", function() { + if (!document.registerElement) + return; + var shadowDomContent = "
before
"; + shadowDomContent += "
"; + shadowDomContent += "
after
"; + hosts = createHostsWithShadowDom([shadowDomContent]); + testRangeWithHosts(hosts); + }); - assert.isTrue('startContainer' in range); - assert.isFalse(range.hasOwnProperty('startContainer')); + test("custom - wrapped and more", function() { + if (!document.registerElement) + return; + var shadowDomContent = "
before
"; + shadowDomContent += "
"; + shadowDomContent += "
after
"; + hosts = createHostsWithShadowDom([shadowDomContent]); + testRangeWithHosts(hosts); + }); + + test("div - wrapped and more", function() { + var shadowDomContent = "
before
"; + shadowDomContent += "
"; + shadowDomContent += "
after
"; + hosts = createHostsWithShadowDom([shadowDomContent],"div"); + testRangeWithHosts(hosts); + }); + + test("div - wrapped and more", function() { + var shadowDomContent = "
before
"; + shadowDomContent += "
"; + shadowDomContent += "
after
"; + hosts = createHostsWithShadowDom([shadowDomContent],"div"); + testRangeWithHosts(hosts); + }); - assert.isTrue('startOffset' in range); - assert.isFalse(range.hasOwnProperty('startOffset')); }); - test('toString', function() { - var range = document.createRange(); - div = document.createElement('div'); - document.body.appendChild(div); - div.innerHTML = 'abc'; - var a = div.firstChild; - var b = a.nextSibling; - range.selectNode(b); - assert.equal(range.toString(), 'b'); + suite("Standard+Custom elements with oldest+youngest Shadow Dom",function(){ + + teardown(function() { + removeHosts(); + }); + + test("div with and ",function(){ + var shadowDomContentsArray = [ + "", + "" + ]; + + hosts = createHostsWithShadowDom(shadowDomContentsArray,"div"); + testRangeWithHosts(hosts); + }); + + test("custom with and ",function(){ + if (!document.registerElement) + return; + + var shadowDomContentsArray = [ + "", + "" + ]; + + hosts = createHostsWithShadowDom(shadowDomContentsArray); + testRangeWithHosts(hosts); + }); + + test("div with wrapped and ",function(){ + var shadowDomContentsArray = [ + "
", + "
" + ]; + + hosts = createHostsWithShadowDom(shadowDomContentsArray,"div"); + testRangeWithHosts(hosts); + }); + + test("custom with wrapped and and more",function(){ + if (!document.registerElement) + return; + + var oldestShadowDom = "
In Oldest shadow dom before
" + + "
" + + "
In Oldest shadow dom after
"; + + var youngestShadowDom = "
In youngest shadow dom before
" + + "
" + + "
In youngest shadow dom after
"; + + var shadowDomContentsArray = [ oldestShadowDom, youngestShadowDom ]; + + hosts = createHostsWithShadowDom(shadowDomContentsArray); + testRangeWithHosts(hosts); + }) + + }); + + suite("multiple with select (not supported)",function(){ + + teardown(function() { + removeHosts(); + }); + + // Maybe someone can make sense of what range in + // different trees means. + function testRangeWithWithFragmentedContent(host){ + + host.innerHTML = "bold1italic1bold2" + + "italic2
some text
"; + + assert.isNotNull(host.shadowRoot); + + // Force rendering for the host with the polyfill + // shadow dom. Of course the host with native shadow + // dom does not need it. + host.offsetWidth; + + var range = createRangeForHost(host); + + // We are using the polyfill selection for native + // and polyfill ranges. It has no impact on the tests results. + var selection = document.getSelection(); + if (selection.rangeCount>0){ + selection.removeAllRanges(); + } + + // Just make sure we do not throw an exception + range.setStart(host,0); + range.setEnd(host,2); + + range.setStart(host,0); + range.setEnd(host,1); + + range.setStart(host,0); + range.setEnd(host,host.childNodes.length+1); + + assert.isTrue(range.startContainer === host); + assert.isTrue(range.endContainer === host); + assert.isTrue(range.commonAncestorContainer === host); + //assert.isTrue(range.toString() === "bold1italic1"); + } + + test.skip("div with multiple wrapped",function(){ + var shadowDomContent = "Bold tags:
" + + "

" + + "Italic tags:
" + + "

" + + "Others:
"; + + hosts = createHostsWithShadowDom([shadowDomContent],"div"); + hosts.forEach(function(host){ + document.body.appendChild(wrapIfNeeded(host)); + testRangeWithWithFragmentedContent(host); + }); + }); + + test.skip("div with multiple ",function(){ + var shadowDomContent = "Bold tags:" + + "
Italic tags:" + + "
Others:"; + + hosts = createHostsWithShadowDom([shadowDomContent],"div"); + hosts.forEach(function(host){ + // I am not sure even the native chrome implementation makes + // sense. The meaning of selecting range in different trees needs to + // be defined. Not sure if it even makes sense. It did not to me. + document.body.appendChild(wrapIfNeeded(host)); + testRangeWithWithFragmentedContent(host); + }); + }); + }); }); diff --git a/tests/ShadowDOM/js/Selection.js b/tests/ShadowDOM/js/Selection.js index 30418f317..d3c25bd7e 100644 --- a/tests/ShadowDOM/js/Selection.js +++ b/tests/ShadowDOM/js/Selection.js @@ -144,6 +144,7 @@ suite('Selection', function() { test('addRange', function() { var selection = window.getSelection(); + selection.removeAllRanges(); var range = document.createRange(); range.selectNode(b); selection.addRange(range); From ce57111b71c76f4c3db7d7f6de3e9e57dcb4c37c Mon Sep 17 00:00:00 2001 From: Emad Eid Date: Fri, 3 Apr 2015 20:30:55 -0400 Subject: [PATCH 4/9] code review changes and code style fixes --- src/ShadowDOM/wrappers/Document.js | 29 +++-- src/ShadowDOM/wrappers/Range.js | 63 +++++------ src/ShadowDOM/wrappers/TreeWalker.js | 6 +- tests/ShadowDOM/js/Range.js | 163 ++++++++++++++------------- 4 files changed, 131 insertions(+), 130 deletions(-) diff --git a/src/ShadowDOM/wrappers/Document.js b/src/ShadowDOM/wrappers/Document.js index 7dc3875ad..e70437592 100644 --- a/src/ShadowDOM/wrappers/Document.js +++ b/src/ShadowDOM/wrappers/Document.js @@ -121,25 +121,31 @@ var originalCreateTreeWalker = document.createTreeWalker; var TreeWalkerWrapper = scope.wrappers.TreeWalker; - Document.prototype.createTreeWalker = function(root,whatToShow, - filter,expandEntityReferences ) { + Document.prototype.createTreeWalker = function(root, whatToShow, + filter, expandEntityReferences) { var newFilter = null; // IE does not like undefined. - // Support filter as a function or object with function defined as acceptNode. - // IE supports filter as a function only. Chrome and FF support both formats. - if (filter){ - if (filter.acceptNode && typeof filter.acceptNode === 'function'){ + // Support filter as a function or object with function defined as + // acceptNode. IE supports filter as a function only. + // Chrome and FF support both formats. + if (filter) { + if (filter.acceptNode && typeof filter.acceptNode === 'function') { newFilter = { - acceptNode:function(node) { return filter.acceptNode(wrap(node)); } + acceptNode: function(node) { + return filter.acceptNode(wrap(node)); + } }; - }else if (typeof filter === 'function'){ - newFilter = function(node) { return filter(wrap(node)); } + } else if (typeof filter === 'function') { + newFilter = function(node) { + return filter(wrap(node)); + } } } - return new TreeWalkerWrapper(originalCreateTreeWalker.call(unwrap(this), unwrap(root), - whatToShow, newFilter, expandEntityReferences )); + return new TreeWalkerWrapper( + originalCreateTreeWalker.call(unwrap(this), unwrap(root), + whatToShow, newFilter, expandEntityReferences)); }; if (document.registerElement) { @@ -154,7 +160,6 @@ if (!prototype) prototype = Object.create(HTMLElement.prototype); - // If we already used the object as a prototype for another custom // element. if (scope.nativePrototypeTable.get(prototype)) { diff --git a/src/ShadowDOM/wrappers/Range.js b/src/ShadowDOM/wrappers/Range.js index 406ffbfc5..e5961aca3 100644 --- a/src/ShadowDOM/wrappers/Range.js +++ b/src/ShadowDOM/wrappers/Range.js @@ -17,36 +17,33 @@ var unwrap = scope.unwrap; var unwrapIfNeeded = scope.unwrapIfNeeded; var wrap = scope.wrap; + var getTreeScope = scope.getTreeScope; var OriginalRange = window.Range; var ShadowRoot = scope.wrappers.ShadowRoot; - function isElement(node){ - return node && node.nodeType == Node.ELEMENT_NODE - } - - function getHost(node){ - while(node && node.parentNode){ - if (node.parentNode && node.parentNode instanceof ShadowRoot){ - return node.parentNode.host; - } - node=node.parentNode; + function getHost(node) { + var root = getTreeScope(node).root; + if (root instanceof ShadowRoot) { + return root.host; } + return null; } - function hostNodeToShadowNode(refNode,offset){ - if (refNode.shadowRoot){ - // Note: if the refNode is an element, then selecting a range with and offset - // equal to refNode.childNodes.length+1 is valid. That is why calling Math.min - // is necessary to make sure we select valid children. - var child = refNode.childNodes[Math.min(refNode.childNodes.length-1, offset)]; - if (child){ - var insertionPoint = ShadowDOMPolyfill.getDestinationInsertionPoints(child); - if (insertionPoint.length>0){ + function hostNodeToShadowNode(refNode, offset) { + if (refNode.shadowRoot) { + // Note: if the refNode is an element, then selecting a range with and + // offset equal to refNode.childNodes.length+1 is valid. That is why + // calling Math.min is necessary to make sure we select valid children. + offset = Math.min(refNode.childNodes.length - 1, offset); + var child = refNode.childNodes[offset]; + if (child) { + var insertionPoint = scope.getDestinationInsertionPoints(child); + if (insertionPoint.length > 0) { var parentNode = insertionPoint[0].parentNode; - if (isElement(parentNode)){ - refNode=parentNode; + if (parentNode.nodeType == Node.ELEMENT_NODE) { + refNode = parentNode; } } } @@ -54,13 +51,9 @@ return refNode; } - function shadowNodeToHostNode(rangeNodeFieldName){ - var node = wrap(unsafeUnwrap(this)[rangeNodeFieldName]); - var host = getHost(node); - if (host){ - return host; - } - return node; + function shadowNodeToHostNode(node) { + node = wrap(node); + return getHost(node) || node; } function Range(impl) { @@ -69,20 +62,20 @@ Range.prototype = { get startContainer() { // Never return a node in the shadow dom. - return shadowNodeToHostNode.call(this,"startContainer"); + return shadowNodeToHostNode(unsafeUnwrap(this).startContainer); }, get endContainer() { - return shadowNodeToHostNode.call(this,"endContainer"); + return shadowNodeToHostNode(unsafeUnwrap(this).endContainer); }, get commonAncestorContainer() { - return shadowNodeToHostNode.call(this,"commonAncestorContainer"); + return shadowNodeToHostNode(unsafeUnwrap(this).commonAncestorContainer); }, - setStart: function(refNode,offset) { - refNode=hostNodeToShadowNode(refNode,offset); + setStart: function(refNode, offset) { + refNode = hostNodeToShadowNode(refNode, offset); unsafeUnwrap(this).setStart(unwrapIfNeeded(refNode), offset); }, - setEnd: function(refNode,offset) { - refNode=hostNodeToShadowNode(refNode,offset); + setEnd: function(refNode, offset) { + refNode = hostNodeToShadowNode(refNode, offset); unsafeUnwrap(this).setEnd(unwrapIfNeeded(refNode), offset); }, setStartBefore: function(refNode) { diff --git a/src/ShadowDOM/wrappers/TreeWalker.js b/src/ShadowDOM/wrappers/TreeWalker.js index 536122f41..25b7c0d15 100644 --- a/src/ShadowDOM/wrappers/TreeWalker.js +++ b/src/ShadowDOM/wrappers/TreeWalker.js @@ -24,16 +24,16 @@ } TreeWalker.prototype = { - get root(){ + get root() { return wrap(unsafeUnwrap(this).root); }, get currentNode() { return wrap(unsafeUnwrap(this).currentNode); }, set currentNode(node) { - unsafeUnwrap(this).currentNode=unwrapIfNeeded(node); + unsafeUnwrap(this).currentNode = unwrapIfNeeded(node); }, - get filter(){ + get filter() { return unsafeUnwrap(this).filter; }, parentNode: function() { diff --git a/tests/ShadowDOM/js/Range.js b/tests/ShadowDOM/js/Range.js index 492b64c34..e31f922b1 100644 --- a/tests/ShadowDOM/js/Range.js +++ b/tests/ShadowDOM/js/Range.js @@ -18,17 +18,18 @@ suite('Range', function() { var hosts; var customElementPrefix = "range-custom-element-"; var nativeCustomElementPrefix = "range-native-element-"; - var customElementIndex=0; - var nativeCustomElementIndex=0; + var customElementIndex = 0; + var nativeCustomElementIndex = 0; setup(function() { - isNativeShadowDomSupported = !!unwrap(document.createElement('div')).createShadowRoot; + isNativeShadowDomSupported = + !!unwrap(document.createElement('div')).createShadowRoot; }); - function removeHosts(){ - if (hosts){ - hosts.forEach(function(host){ - if (host && host.parentNode){ + function removeHosts() { + if (hosts) { + hosts.forEach(function(host) { + if (host && host.parentNode) { host.parentNode.removeChild(wrapIfNeeded(host)); } }); @@ -36,44 +37,44 @@ suite('Range', function() { } } - function getNewCustomElementType(){ - return customElementPrefix+customElementIndex++; + function getNewCustomElementType() { + return customElementPrefix + customElementIndex++; } - function getNewNativeCustomElementType(){ - return nativeCustomElementPrefix+nativeCustomElementIndex++; + function getNewNativeCustomElementType() { + return nativeCustomElementPrefix + nativeCustomElementIndex++; } - function createCustomElement(name,shadowDomContentsArray,native){ + function createCustomElement(name, shadowDomContentsArray, native) { var prototype = Object.create(HTMLElement.prototype); prototype.createdCallback = function() { var element = this; - if (native){ + if (native) { element = unwrap(this); } - createShadowDom(element,shadowDomContentsArray); + createShadowDom(element, shadowDomContentsArray); }; - return document.registerElement(name, {prototype:prototype}); + return document.registerElement(name, {prototype: prototype}); } // If the host has native shadow dom then we need to return native // range. Native range is just a polyfill Range unwrapped. - function createRangeForHost(host){ + function createRangeForHost(host) { var range = document.createRange(); // If we are dealing with native shadow dom, expose the range object // as a native range object by just unwrapping it. //noinspection JSUnresolvedVariable - if (hasNativeShadowRoot(host)){ + if (hasNativeShadowRoot(host)) { range = unwrap(range); } return range; } - function hasNativeShadowRoot(node){ + function hasNativeShadowRoot(node) { return node && node.shadowRoot && !(node.shadowRoot instanceof ShadowRoot); } @@ -84,12 +85,13 @@ suite('Range', function() { } function createStandardElementWithPolyfillShadowDom(shadowDomContentsArray, - elementType){ + elementType) { var element = document.createElement(elementType); - return createShadowDom(element,shadowDomContentsArray); + return createShadowDom(element, shadowDomContentsArray); } - function createHostWithPolyfillShadowDom(shadowDomContentsArray, elementType){ + function createHostWithPolyfillShadowDom(shadowDomContentsArray, + elementType) { if (!elementType) { return createCustomElementWithPolyfillShadowDom(shadowDomContentsArray); } else { @@ -98,20 +100,20 @@ suite('Range', function() { } } - function createShadowDom(element,shadowDomContentsArray){ - shadowDomContentsArray.forEach(function(shadowDomContent){ - element.createShadowRoot().innerHTML=shadowDomContent; + function createShadowDom(element, shadowDomContentsArray) { + shadowDomContentsArray.forEach(function(shadowDomContent) { + element.createShadowRoot().innerHTML = shadowDomContent; }); return element; } function createStandardElementWithNativeShadowDom(shadowDomContentsArray, - elementType){ + elementType) { var element = document.createElement(elementType); - return createShadowDom(unwrap(element),shadowDomContentsArray); + return createShadowDom(unwrap(element), shadowDomContentsArray); } - function createCustomElementWithNativeShadowDom(shadowDomContentsArray){ + function createCustomElementWithNativeShadowDom(shadowDomContentsArray) { var element; var nativeElementType = getNewNativeCustomElementType(); createCustomElement(nativeElementType, shadowDomContentsArray, true); @@ -120,16 +122,16 @@ suite('Range', function() { assert.isNotNull(element.shadowRoot); } - function createHostWithNativeShadowDom(shadowDomContentsArray, elementType){ + function createHostWithNativeShadowDom(shadowDomContentsArray, elementType) { - if (!isNativeShadowDomSupported){ + if (!isNativeShadowDomSupported) { return; } - if (!elementType){ + if (!elementType) { return createCustomElementWithNativeShadowDom(shadowDomContentsArray, elementType); - }else{ + } else { return createStandardElementWithNativeShadowDom(shadowDomContentsArray, elementType); } @@ -139,7 +141,7 @@ suite('Range', function() { // if available. The two hosts then will be tested by setting // the innerHTML of those hosts and using the polyfill range or // the native range. The results should be the same in both cases. - function createHostsWithShadowDom(shadowDomContentsArray, elementType){ + function createHostsWithShadowDom(shadowDomContentsArray, elementType) { var hostWithPolyFillShadowDom = createHostWithPolyfillShadowDom(shadowDomContentsArray, elementType); @@ -150,7 +152,7 @@ suite('Range', function() { assert.isObject(hostWithPolyFillShadowDom); hosts.push(hostWithPolyFillShadowDom); - if (hostWithNativeShadowDom){ + if (hostWithNativeShadowDom) { hosts.push(hostWithNativeShadowDom); } @@ -161,7 +163,7 @@ suite('Range', function() { // have native shadow dom or polyfill shadow dom. Then we start selecting // the range based on the set innerHTML and the range has to work // regardless of the structure of the shadow dom. - function testRangeWith3SpansHTML(host){ + function testRangeWith3SpansHTML(host) { host.innerHTML = "OneTwoThree"; @@ -176,15 +178,15 @@ suite('Range', function() { // We are using the polyfill selection for native and polyfill ranges. // It has no impact on the tests results. var selection = document.getSelection(); - if (selection.rangeCount>0){ + if (selection.rangeCount > 0) { selection.removeAllRanges(); } // We do not really have to add the range to the selection. // It provides visual feedback of the range while we are debugging. - range.setStart(host,0); - range.setEnd(host,2); + range.setStart(host, 0); + range.setEnd(host, 2); selection.addRange(range); assert.isTrue(range.startContainer === host); @@ -192,26 +194,26 @@ suite('Range', function() { assert.isTrue(range.commonAncestorContainer === host); assert.isTrue(range.toString() === "OneTwo"); - range.setStart(host,0); - range.setEnd(host,1); + range.setStart(host, 0); + range.setEnd(host, 1); assert.isTrue(range.toString() === "One"); selection.removeAllRanges(); selection.addRange(range); - range.setStart(host,1); - range.setEnd(host,2); + range.setStart(host, 1); + range.setEnd(host, 2); assert.isTrue(range.toString() === "Two"); selection.removeAllRanges(); selection.addRange(range); - range.setStart(host,2); - range.setEnd(host,3); + range.setStart(host, 2); + range.setEnd(host, 3); assert.isTrue(range.toString() === "Three"); selection.removeAllRanges(); selection.addRange(range); - range.setStart(host,0); - range.setEnd(host,3); + range.setStart(host, 0); + range.setEnd(host, 3); assert.isTrue(range.toString() === "OneTwoThree"); selection.removeAllRanges(); selection.addRange(range); @@ -231,21 +233,21 @@ suite('Range', function() { // Test selecting the text nodes inside the spans var span0TextNode = span0.childNodes[0]; var span2TextNode = span2.childNodes[0]; - range.setStart(span0TextNode,1); - range.setEnd(span2TextNode,1); + range.setStart(span0TextNode, 1); + range.setEnd(span2TextNode, 1); selection.removeAllRanges(); selection.addRange(range); assert.isTrue(range.toString() === "neTwoT"); } - function testRangeWithHosts(hosts){ - hosts.forEach(function(host){ + function testRangeWithHosts(hosts) { + hosts.forEach(function(host) { document.body.appendChild(wrapIfNeeded(host)); testRangeWith3SpansHTML(host); }); } - suite('Standard elements (no Shadow Dom)',function(){ + suite('Standard elements (no Shadow Dom)', function() { var div; teardown(function() { @@ -319,7 +321,7 @@ suite('Range', function() { }); - suite('Standard+Custom elements with Shadow Dom',function(){ + suite('Standard+Custom elements with Shadow Dom', function() { teardown(function() { removeHosts(); @@ -383,7 +385,7 @@ suite('Range', function() { var shadowDomContent = "
before
"; shadowDomContent += "
"; shadowDomContent += "
after
"; - hosts = createHostsWithShadowDom([shadowDomContent],"div"); + hosts = createHostsWithShadowDom([shadowDomContent], "div"); testRangeWithHosts(hosts); }); @@ -391,29 +393,29 @@ suite('Range', function() { var shadowDomContent = "
before
"; shadowDomContent += "
"; shadowDomContent += "
after
"; - hosts = createHostsWithShadowDom([shadowDomContent],"div"); + hosts = createHostsWithShadowDom([shadowDomContent], "div"); testRangeWithHosts(hosts); }); }); - suite("Standard+Custom elements with oldest+youngest Shadow Dom",function(){ + suite("Standard+Custom elements with oldest+youngest Shadow Dom", function() { teardown(function() { removeHosts(); }); - test("div with and ",function(){ + test("div with and ", function() { var shadowDomContentsArray = [ "", "" ]; - hosts = createHostsWithShadowDom(shadowDomContentsArray,"div"); + hosts = createHostsWithShadowDom(shadowDomContentsArray, "div"); testRangeWithHosts(hosts); }); - test("custom with and ",function(){ + test("custom with and ", function() { if (!document.registerElement) return; @@ -426,29 +428,29 @@ suite('Range', function() { testRangeWithHosts(hosts); }); - test("div with wrapped and ",function(){ + test("div with wrapped and ", function() { var shadowDomContentsArray = [ "
", "
" ]; - hosts = createHostsWithShadowDom(shadowDomContentsArray,"div"); + hosts = createHostsWithShadowDom(shadowDomContentsArray, "div"); testRangeWithHosts(hosts); }); - test("custom with wrapped and and more",function(){ + test("custom with wrapped and and more", function() { if (!document.registerElement) return; - var oldestShadowDom = "
In Oldest shadow dom before
" + + var oldestShadowDom = "
In Oldest shadow dom before
" + "
" + "
In Oldest shadow dom after
"; - var youngestShadowDom = "
In youngest shadow dom before
" + + var youngestShadowDom = "
In youngest shadow dom before
" + "
" + "
In youngest shadow dom after
"; - var shadowDomContentsArray = [ oldestShadowDom, youngestShadowDom ]; + var shadowDomContentsArray = [oldestShadowDom, youngestShadowDom]; hosts = createHostsWithShadowDom(shadowDomContentsArray); testRangeWithHosts(hosts); @@ -456,7 +458,7 @@ suite('Range', function() { }); - suite("multiple with select (not supported)",function(){ + suite("multiple with select (not supported)", function() { teardown(function() { removeHosts(); @@ -464,10 +466,11 @@ suite('Range', function() { // Maybe someone can make sense of what range in // different trees means. - function testRangeWithWithFragmentedContent(host){ + function testRangeWithWithFragmentedContent(host) { - host.innerHTML = "bold1italic1bold2" + - "italic2
some text
"; + host.innerHTML = "bold1italic1" + + "bold2italic2" + + "
some text
"; assert.isNotNull(host.shadowRoot); @@ -481,19 +484,19 @@ suite('Range', function() { // We are using the polyfill selection for native // and polyfill ranges. It has no impact on the tests results. var selection = document.getSelection(); - if (selection.rangeCount>0){ + if (selection.rangeCount > 0) { selection.removeAllRanges(); } // Just make sure we do not throw an exception - range.setStart(host,0); - range.setEnd(host,2); + range.setStart(host, 0); + range.setEnd(host, 2); - range.setStart(host,0); - range.setEnd(host,1); + range.setStart(host, 0); + range.setEnd(host, 1); - range.setStart(host,0); - range.setEnd(host,host.childNodes.length+1); + range.setStart(host, 0); + range.setEnd(host, host.childNodes.length + 1); assert.isTrue(range.startContainer === host); assert.isTrue(range.endContainer === host); @@ -501,27 +504,27 @@ suite('Range', function() { //assert.isTrue(range.toString() === "bold1italic1"); } - test.skip("div with multiple wrapped",function(){ + test.skip("div with multiple wrapped", function() { var shadowDomContent = "Bold tags:
" + "

" + "Italic tags:
" + "

" + "Others:
"; - hosts = createHostsWithShadowDom([shadowDomContent],"div"); - hosts.forEach(function(host){ + hosts = createHostsWithShadowDom([shadowDomContent], "div"); + hosts.forEach(function(host) { document.body.appendChild(wrapIfNeeded(host)); testRangeWithWithFragmentedContent(host); }); }); - test.skip("div with multiple ",function(){ + test.skip("div with multiple ", function() { var shadowDomContent = "Bold tags:" + "
Italic tags:" + "
Others:"; - hosts = createHostsWithShadowDom([shadowDomContent],"div"); - hosts.forEach(function(host){ + hosts = createHostsWithShadowDom([shadowDomContent], "div"); + hosts.forEach(function(host) { // I am not sure even the native chrome implementation makes // sense. The meaning of selecting range in different trees needs to // be defined. Not sure if it even makes sense. It did not to me. From edde6bbd577f21306975c67313caf680af0f9f1a Mon Sep 17 00:00:00 2001 From: Evgeniy Drachev Date: Mon, 6 Apr 2015 22:54:50 +0300 Subject: [PATCH 5/9] Added possibility for recursive calls via callback for forDocumentTree function --- src/CustomElements/traverse.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/CustomElements/traverse.js b/src/CustomElements/traverse.js index e8e8a4302..9ab9ce5fe 100644 --- a/src/CustomElements/traverse.js +++ b/src/CustomElements/traverse.js @@ -58,19 +58,12 @@ function forRoots(node, cb) { } } -/* -Note that the import tree can consume itself and therefore special care -must be taken to avoid recursion. -*/ -var processingDocuments; function forDocumentTree(doc, cb) { - processingDocuments = []; - _forDocumentTree(doc, cb); - processingDocuments = null; + _forDocumentTree(doc, cb, []); } -function _forDocumentTree(doc, cb) { +function _forDocumentTree(doc, cb, processingDocuments) { doc = wrap(doc); if (processingDocuments.indexOf(doc) >= 0) { return; @@ -79,7 +72,7 @@ function _forDocumentTree(doc, cb) { var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']'); for (var i=0, l=imports.length, n; (i Date: Mon, 6 Apr 2015 15:10:57 -0700 Subject: [PATCH 6/9] Make the CustomElements polyfill upgrade elements created by `document.importNode` --- src/CustomElements/boot.js | 24 ++--------- src/CustomElements/register.js | 49 +++++++++++++++++------ tests/CustomElements/js/customElements.js | 16 ++++++++ 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/src/CustomElements/boot.js b/src/CustomElements/boot.js index 14e4bb7dc..2eeb40584 100644 --- a/src/CustomElements/boot.js +++ b/src/CustomElements/boot.js @@ -15,27 +15,6 @@ var initializeModules = scope.initializeModules; var isIE11OrOlder = /Trident/.test(navigator.userAgent); -// Patch document.importNode to work around IE11 bug that -// casues children of a document fragment imported while -// there is a mutation observer to not have a parentNode (!?!) -if (isIE11OrOlder) { - (function() { - var importNode = document.importNode; - document.importNode = function() { - var n = importNode.apply(document, arguments); - // Copy all children to a new document fragment since - // this one may be broken - if (n.nodeType == n.DOCUMENT_FRAGMENT_NODE) { - var f = document.createDocumentFragment(); - f.appendChild(n); - return f; - } else { - return n; - } - }; - })(); -} - // If native, setup stub api and bail. // NOTE: we fire `WebComponentsReady` under native for api compatibility if (useNative) { @@ -139,4 +118,7 @@ if (document.readyState === 'complete' || scope.flags.eager) { window.addEventListener(loadEvent, bootstrap); } +// exports +scope.isIE11OrOlder = isIE11OrOlder; + })(window.CustomElements); diff --git a/src/CustomElements/register.js b/src/CustomElements/register.js index 45d9fc239..656920613 100644 --- a/src/CustomElements/register.js +++ b/src/CustomElements/register.js @@ -21,8 +21,9 @@ CustomElements.addModule(function(scope) { // imports +var isIE11OrOlder = scope.isIE11OrOlder; var upgradeDocumentTree = scope.upgradeDocumentTree; -var upgrade = scope.upgrade; +var upgradeAll = scope.upgradeAll; var upgradeWithDefinition = scope.upgradeWithDefinition; var implementPrototype = scope.implementPrototype; var useNative = scope.useNative; @@ -291,20 +292,9 @@ function createElement(tag, typeExtension) { return element; } -function cloneNode(deep) { - // call original clone - var n = domCloneNode.call(this, deep); - // upgrade the element and subtree - upgrade(n); - // return the clone - return n; -} - // capture native createElement before we override it var domCreateElement = document.createElement.bind(document); var domCreateElementNS = document.createElementNS.bind(document); -// capture native cloneNode before we override it -var domCloneNode = Node.prototype.cloneNode; // Create a custom 'instanceof'. This is necessary when CustomElements // are implemented via a mixin strategy, as for example on IE10. @@ -329,11 +319,44 @@ if (!Object.__proto__ && !useNative) { }; } +// wrap a dom object method that works on nodes such that it forces upgrade +function wrapDomMethodToForceUpgrade(obj, methodName) { + var orig = obj[methodName]; + obj[methodName] = function() { + var n = orig.apply(this, arguments); + upgradeAll(n); + return n; + }; +} + +wrapDomMethodToForceUpgrade(Node.prototype, 'cloneNode'); +wrapDomMethodToForceUpgrade(document, 'importNode'); + +// Patch document.importNode to work around IE11 bug that +// casues children of a document fragment imported while +// there is a mutation observer to not have a parentNode (!?!) +if (isIE11OrOlder) { + (function() { + var importNode = document.importNode; + document.importNode = function() { + var n = importNode.apply(document, arguments); + // Copy all children to a new document fragment since + // this one may be broken + if (n.nodeType == n.DOCUMENT_FRAGMENT_NODE) { + var f = document.createDocumentFragment(); + f.appendChild(n); + return f; + } else { + return n; + } + }; + })(); +} + // exports document.registerElement = register; document.createElement = createElement; // override document.createElementNS = createElementNS; // override -Node.prototype.cloneNode = cloneNode; // override scope.registry = registry; scope.instanceof = isInstance; scope.reservedTagList = reservedTagList; diff --git a/tests/CustomElements/js/customElements.js b/tests/CustomElements/js/customElements.js index 48a06632b..398c9c553 100644 --- a/tests/CustomElements/js/customElements.js +++ b/tests/CustomElements/js/customElements.js @@ -364,6 +364,22 @@ suite('customElements', function() { done(); }); + test('document.importNode upgrades', function() { + var XImportPrototype = Object.create(HTMLElement.prototype); + XImportPrototype.createdCallback = function() { + this.__ready__ = true; + }; + document.registerElement('x-import', { + prototype: XImportPrototype + }); + var frag = document.createDocumentFragment(); + frag.appendChild(document.createElement('x-import')); + assert.isTrue(frag.firstChild.__ready__, 'source element upgraded'); + var imported = document.importNode(frag, true); + window.imported = imported; + assert.isTrue(imported.firstChild.__ready__, 'imported element upgraded'); + }); + test('entered left apply to view', function() { var invocations = []; var elementProto = Object.create(HTMLElement.prototype); From 878d628e3014592a487a0d9679d4c8566718a89b Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Wed, 18 Mar 2015 08:40:58 +0100 Subject: [PATCH 7/9] Add support for the >>> selector in ShadowCSS ">>>" is the new name for "/deep/" see http://dev.w3.org/csswg/css-scoping-1/#deep-combinator --- src/ShadowCSS/ShadowCSS.js | 14 +++++++------ src/ShadowDOM/querySelector.js | 4 ++-- tests/ShadowCSS/html/combinators-shadow.html | 16 +++++++++++++-- tests/ShadowDOM/js/Document.js | 21 +++++++++++++------- tests/ShadowDOM/js/Element.js | 20 +++++++++++-------- 5 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/ShadowCSS/ShadowCSS.js b/src/ShadowCSS/ShadowCSS.js index c2468db4e..bee43ba8a 100644 --- a/src/ShadowCSS/ShadowCSS.js +++ b/src/ShadowCSS/ShadowCSS.js @@ -578,13 +578,15 @@ var selectorRe = /([^{]*)({[\s\S]*?})/gim, polyfillHostRe = new RegExp(polyfillHost, 'gim'), polyfillHostContextRe = new RegExp(polyfillHostContext, 'gim'), shadowDOMSelectorsRe = [ - /\^\^/g, - /\^/g, - /\/shadow\//g, - /\/shadow-deep\//g, + />>>/g, /::shadow/g, - /\/deep\//g, - /::content/g + /::content/g, + // Deprecated selectors + /\/deep\//g, // former >>> + /\/shadow\//g, // former ::shadow + /\/shadow-deep\//g, // former /deep/ + /\^\^/g, // former /shadow/ + /\^/g // former /shadow-deep/ ]; function stylesToCssText(styles, preserveComments) { diff --git a/src/ShadowDOM/querySelector.js b/src/ShadowDOM/querySelector.js index 3a7e9de52..adf736431 100644 --- a/src/ShadowDOM/querySelector.js +++ b/src/ShadowDOM/querySelector.js @@ -49,7 +49,7 @@ } function shimSelector(selector) { - return String(selector).replace(/\/deep\/|::shadow/g, ' '); + return String(selector).replace(/\/deep\/|::shadow|>>>/g, ' '); } function shimMatchesSelector(selector) { @@ -71,7 +71,7 @@ ) // From ShadowCSS, will be replaced by space .replace( - /\^|\/shadow\/|\/shadow-deep\/|::shadow|\/deep\/|::content/g, + /\^|\/shadow\/|\/shadow-deep\/|::shadow|\/deep\/|::content|>>>/g, ' ' ); } diff --git a/tests/ShadowCSS/html/combinators-shadow.html b/tests/ShadowCSS/html/combinators-shadow.html index 3b61e67f8..ffdea389d 100644 --- a/tests/ShadowCSS/html/combinators-shadow.html +++ b/tests/ShadowCSS/html/combinators-shadow.html @@ -40,6 +40,10 @@ x-bar /deep/ .zot { color: white; } + + x-bar >>> .zot-2 { + color: white; + }
orange
green
@@ -58,6 +62,7 @@ @@ -84,11 +89,18 @@ var xZot = foo.shadowRoot.querySelector('x-bar').shadowRoot .querySelector('x-zot'); var zot = xZot.shadowRoot.querySelector('.zot'); + var zot2 = xZot.shadowRoot.querySelector('.zot-2'); chai.assert.equal(getComputedStyle(zot).backgroundColor, 'rgb(0, 128, 0)', - '^ styles are applied (backgroundColor)'); + '::shadow styles are applied (backgroundColor)'); chai.assert.equal(getComputedStyle(zot).color, 'rgb(255, 255, 255)', - '^^ styles are applied (color)'); + '/deep/ styles are applied (color)'); + + // TODO(vicb): remove the if when >>> get supported by browsers + if (window.ShadowDOMPolyfill) { + chai.assert.equal(getComputedStyle(zot2).color, 'rgb(255, 255, 255)', + '>>> styles are applied (color)'); + } // NOTE: native import styles not shimmed under SD polyfill var unsupported = window.ShadowDOMPolyfill && HTMLImports.useNative; diff --git a/tests/ShadowDOM/js/Document.js b/tests/ShadowDOM/js/Document.js index 45f245340..4b4938b22 100644 --- a/tests/ShadowDOM/js/Document.js +++ b/tests/ShadowDOM/js/Document.js @@ -216,6 +216,9 @@ htmlSuite('Document', function() { assert.equal(aa1, document.querySelector('div /deep/ aa')); assert.equal(bb, document.querySelector('div /deep/ bb')); + + assert.equal(aa1, document.querySelector('div >>> aa')); + assert.equal(bb, document.querySelector('div >>> bb')); }); test('querySelectorAll deep', function() { @@ -230,14 +233,18 @@ htmlSuite('Document', function() { div.offsetHeight; - var list = document.querySelectorAll('div /deep/ aa'); - assert.equal(2, list.length); - assert.equal(aa1, list[0]); - assert.equal(aa2, list[1]); + ['div /deep/ aa', 'div >>> aa'].forEach(function(selector) { + var list = div.querySelectorAll(selector); + assert.equal(2, list.length); + assert.equal(aa1, list[0]); + assert.equal(aa2, list[1]); + }); - list = document.querySelectorAll('div /deep/ bb'); - assert.equal(1, list.length); - assert.equal(bb, list[0]); + ['div /deep/ bb', 'div >>> bb'].forEach(function(selector) { + var list = div.querySelectorAll(selector); + assert.equal(1, list.length); + assert.equal(bb, list[0]); + }); }); test('addEventListener', function() { diff --git a/tests/ShadowDOM/js/Element.js b/tests/ShadowDOM/js/Element.js index 4158ac7f9..2d50eb394 100644 --- a/tests/ShadowDOM/js/Element.js +++ b/tests/ShadowDOM/js/Element.js @@ -128,14 +128,18 @@ suite('Element', function() { div.offsetHeight; - var list = div.querySelectorAll('div /deep/ aa'); - assert.equal(2, list.length); - assert.equal(aa1, list[0]); - assert.equal(aa2, list[1]); - - list = div.querySelectorAll('div /deep/ bb'); - assert.equal(1, list.length); - assert.equal(bb, list[0]); + ['div /deep/ aa', 'div >>> aa'].forEach(function(selector) { + var list = div.querySelectorAll(selector); + assert.equal(2, list.length); + assert.equal(aa1, list[0]); + assert.equal(aa2, list[1]); + }); + + ['div /deep/ bb', 'div >>> bb'].forEach(function(selector) { + var list = div.querySelectorAll(selector); + assert.equal(1, list.length); + assert.equal(bb, list[0]); + }); }); test('querySelectorAll ::shadow', function() { From ce27273b4d59a5f4cad25e2a950b48f041002602 Mon Sep 17 00:00:00 2001 From: AJ Ortega Date: Mon, 13 Apr 2015 13:34:52 -0700 Subject: [PATCH 8/9] Prepare for 0.6.1, add MO everywhere it's used. --- src/CustomElements/CustomElements.js | 1 + src/CustomElements/build.json | 1 + src/HTMLImports/HTMLImports.js | 1 + src/HTMLImports/build.json | 1 + src/WebComponents/build/boot.js | 8 ++++---- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/CustomElements/CustomElements.js b/src/CustomElements/CustomElements.js index 7b770b4c8..8ef94e6a9 100644 --- a/src/CustomElements/CustomElements.js +++ b/src/CustomElements/CustomElements.js @@ -28,6 +28,7 @@ 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 1723a0ae7..f8407cd15 100644 --- a/src/CustomElements/build.json +++ b/src/CustomElements/build.json @@ -1,5 +1,6 @@ [ "../WeakMap/WeakMap.js", + "../MutationObserver/MutationObserver.js", "base.js", "traverse.js", "observe.js", diff --git a/src/HTMLImports/HTMLImports.js b/src/HTMLImports/HTMLImports.js index bf3f12ddf..2b76f5d00 100644 --- a/src/HTMLImports/HTMLImports.js +++ b/src/HTMLImports/HTMLImports.js @@ -27,6 +27,7 @@ var file = 'HTMLImports.js'; var modules = [ '../WeakMap/WeakMap.js', + '../MutationObserver/MutationObserver.js', 'base.js', 'module.js', 'path.js', diff --git a/src/HTMLImports/build.json b/src/HTMLImports/build.json index cad3be5fc..635120f24 100644 --- a/src/HTMLImports/build.json +++ b/src/HTMLImports/build.json @@ -1,5 +1,6 @@ [ "../WeakMap/WeakMap.js", + "../MutationObserver/MutationObserver.js", "base.js", "module.js", "path.js", diff --git a/src/WebComponents/build/boot.js b/src/WebComponents/build/boot.js index a0e69277e..54c662f28 100644 --- a/src/WebComponents/build/boot.js +++ b/src/WebComponents/build/boot.js @@ -11,10 +11,10 @@ window.WebComponents = window.WebComponents || {}; // process flags (function(scope){ - + // import var flags = scope.flags || {}; - + var file = 'webcomponents.js'; var script = document.querySelector('script[src*="' + file + '"]'); @@ -34,7 +34,7 @@ window.WebComponents = window.WebComponents || {}; } } // log flags - if (flags.log) { + if (flags.log && flags.log.split) { var parts = flags.log.split(','); flags.log = {}; parts.forEach(function(f) { @@ -48,7 +48,7 @@ window.WebComponents = window.WebComponents || {}; // Determine default settings. // If any of these flags match 'native', then force native ShadowDOM; any // other truthy value, or failure to detect native - // ShadowDOM, results in polyfill + // ShadowDOM, results in polyfill flags.shadow = (flags.shadow || flags.shadowdom || flags.polyfill); if (flags.shadow === 'native') { flags.shadow = false; From ed39ec057b5260ca5f715f248da17ab6a26cb9a4 Mon Sep 17 00:00:00 2001 From: AJ Ortega Date: Mon, 13 Apr 2015 13:35:38 -0700 Subject: [PATCH 9/9] bump version --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 0b3db9710..36d2b0e5c 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "webcomponentsjs", "main": "webcomponents.js", - "version": "0.6.0", + "version": "0.6.1", "homepage": "http://webcomponents.org", "authors": [ "The Polymer Authors" diff --git a/package.json b/package.json index 43eef2797..65b003e8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webcomponents.js", - "version": "0.6.0", + "version": "0.6.1", "description": "webcomponents.js", "main": "webcomponents.js", "directories": {