├── .eslintrc.json ├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── externs └── shadydom.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── array-splice.js ├── attach-shadow.js ├── flush.js ├── innerHTML.js ├── link-nodes.js ├── observe-changes.js ├── patch-events.js ├── patch-instances.js ├── patch-native.js ├── patch-prototypes.js ├── patch-shadyRoot.js ├── patches │ ├── Document.js │ ├── DocumentOrFragment.js │ ├── DocumentOrShadowRoot.js │ ├── Element.js │ ├── ElementOrShadowRoot.js │ ├── EventTarget.js │ ├── HTMLElement.js │ ├── Node.js │ ├── ParentNode.js │ ├── ShadowRoot.js │ ├── Slot.js │ ├── Slotable.js │ └── Window.js ├── shady-data.js ├── shadydom.js ├── style-scoping.js ├── utils.js └── wrapper.js ├── tests ├── .eslintrc.json ├── active-element.html ├── attach-while-loading.html ├── connect-disconnect.html ├── event-path.html ├── filter-mutations.html ├── loader.js ├── native-access.html ├── observeChildren.html ├── perf │ └── composed-children-perf.html ├── prefer-performance.html ├── runner.html ├── shady-content.html ├── shady-dynamic.html ├── shady.html ├── slot-scenarios.html ├── slotchange.html ├── smoke │ ├── click.html │ ├── data-table.html │ ├── focus-api.html │ ├── focus.html │ ├── property-info.html │ ├── rel.html │ ├── smoke-nopatch.html │ └── smoke.html ├── sync-style-scoping.html └── wct-browser-config.js └── wct.conf.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "browser": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module" 10 | }, 11 | "plugins": [ 12 | "html" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | shadydom.min.* binary 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @azakus @sorvell 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ### Description 3 | 4 | 5 | #### Live Demo 6 | 7 | http://jsbin.com/caxiwoc/edit?html,console,output 8 | 9 | #### Steps to Reproduce 10 | 18 | 19 | 20 | #### Expected Results 21 | 22 | 23 | #### Actual Results 24 | 25 | 26 | ### Browsers Affected 27 | 28 | - [ ] Chrome 29 | - [ ] Firefox 30 | - [ ] Edge 31 | - [ ] Safari 9 32 | - [ ] Safari 8 33 | - [ ] IE 11 34 | 35 | ### Versions 36 | - webcomponents: vX.X.X 37 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ### Reference Issue 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | shadydom.min.js* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: '9' 3 | dist: trusty 4 | sudo: false 5 | addons: 6 | firefox: latest 7 | chrome: stable 8 | before_script: 9 | - npm run lint 10 | - npm run build 11 | script: 12 | - xvfb-run wct 13 | - if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct -s 'windows 10/microsoftedge@15' -s 'windows 10/microsoftedge@17' -s 'windows 8.1/internet explorer@11' -s 'os x 10.11/safari@9' -s 'os x 10.12/safari@10' -s 'os x 10.13/safari@11' -s 'Linux/chrome@41'; fi 14 | env: 15 | global: 16 | - secure: sXR3QH57zDgwBluJLsz2I8mz22Nuw59TtGvvUFh64hv6PoA3X9GI0uYYaZyVDFfqoOKsjvNnxe2OQDa/mdyWrmAHW8W9DmT0jYJ35HABT7LgKFcOdvdbGhv+/0CxWOsV8aanRNMU30QXV9/+DOg5Q+P0v5m6uxkSjAKZLHEDSQu5RKlHYr5SJfLt8ODdT4onrcmUOi6I1OGDD1HEwMo7EEOst5O5JqvuqW5PU1MqE2v+K1V6ENMqwikgrHcFZ9r0MeTk7kpC4Vp/Fe0mc1M26irDrwGCZJHl8lZKxx2unCGQVIwGEOv4XzyvKXi5VRO3cMt7EZPkGtV7yjcnElIrfjWYDv7PnyPPmDigQIh1uN7iocgLwT2JgtylHGCZwRWOuhxWcmPejpDnjYBSq8mPescGZE2yuKwexlxTo4QeCBrHzcyYWt6bHRorAdVYaTLKcIdx5UGzu0gLJ4iLnkzEcu97MiKVN0md3TaEF0XcTfXVhCacF+p1WXQ+XIcrNMhmn+tx0pkBTTdn6G5Mz89+R8aQFOIblQJNlbhsbhLgJYW7ov0Grx3tmaPAW0/uErcg3WHdRUEIa2ibsDUNvYmBKwyI1ocroNnF68g/PYj/PNUTcfdcccn3iBDtREfRBM+Cvm6Nted2CcJB6BRYaYaYJ8Pt2kTGjUI2nrwVKtyrlNY= 17 | - secure: QR1ATHjwvkeaqcbpSvjoJpAiKQTbZvZhYfgN0ndSp61NNWKefHpcU21RihH1ZvuDAGmYnBNQOt97NAawYra5iz/E36Dv2gwFbEYRT4LJXcbQuqICFsJa+ZrIdUJKf6qxk2ocxU6WM+OMWR+t5wn2iDjmAUdOK6YauMkQrjZayfrNr9QA0Kr7IS0Ra8gxqdlAnDMPPQArXFOF7zrdtcVVcx3l1Okv61mNI8+sBcVx3oBqEW3cy8f1HYKM0WIeJd2AJmOSnRjgYpBlDzNG36hA7WESXmCJRXzvXJ1L8/ib2RYL9ptXAT+8GhPGrLa0gZ/b6elwU/drqzx900kbb5X3itt0mo9sX3ZisC5i/Q9Ge1zIprboe+JOKTtILDKV63wabYf6SUjzVNaaDFH1vxzFWUN6jX9sr8DWci4sxgDPCEn7fXx0GMPm7hx7cO5dw1586sE7Ow31Z1ky6jZDbPszn/i0Fv46q5c8GTdFKDd4v9N/9p6z4HAqVlBq2eeVmD3TH5oMpmvDZ7wkJrxePmDKLy90YX2+dd/CMQQ53NMIBDVZzpfLzvgODS4er9mon0Y4VTNBcLS3AoI9qJdZTWwcYV0tZkYXJLg+x9Kgq0aUJoEeDJ9IEbCAMIE6SwUlBFdL51AKqiQXU8/7nVNtiVAzTCSviI2g8G9/MOICvpcgCz8= 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Filing bugs 2 | 3 | If you find an issue, please do file it on the repository. The [webcomponents/shadydom issues](https://github.com/webcomponents/shadydom/issues) should be used only for issues with shadydom itself - bugs related to creating shadowRoots. 4 | 5 | Please file issues using the issue template provided, filling out as many fields as possible. We love examples for addressing issues - issues with a Plunkr, jsFiddle, or jsBin will be much easier for us to work on quickly. You can start with [this jsbin](http://jsbin.com/caxiwoc/edit?html,console,output) which sets up the basics to create a custom element with a shadowRoot. 6 | 7 | Occasionally we'll close issues if they appear stale or are too vague - please don't take this personally! Please feel free to re-open issues we've closed if there's something we've missed and they still need to be addressed. 8 | 9 | # Contributing Pull Requests 10 | 11 | PR's are even better than issues. We gladly accept community pull requests. In general, there are a few necessary steps before we can accept a pull request: 12 | 13 | - Open an issue describing the problem that you are looking to solve in your PR (if one is not already open), and your approach to solving it. This makes it easier to have a conversation around the best general approach for solving your problem, outside of the code itself. 14 | - Fork the repo you're making the fix on to your own GitHub account. 15 | - Code! 16 | - Ideally, squash your commits into a single commit with a clear message of what the PR does. If it absolutely makes sense to keep multiple commits, that's OK - or perhaps consider making two separate PR's. 17 | - **Include tests that test the range of behavior that changes with your PR.** If you PR fixes a bug, make sure your tests capture that bug. If your PR adds new behavior, make sure that behavior is fully tested. Every PR *must* include associated tests. 18 | - Submit your PR, making sure it references the issue you created. 19 | - If your PR fixes a bug, make sure the issue includes clear steps to reproduce the bug so we can test your fix. 20 | 21 | If you've completed all of these steps we will do its best to respond to the PR as soon as possible. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Everything in this repo is BSD style license unless otherwise specified. 4 | 5 | Copyright (c) 2015 The Polymer Authors. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following disclaimer 13 | in the documentation and/or other materials provided with the 14 | distribution. 15 | * Neither the name of Google Inc. nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 🚨 Moved to [`webcomponents/polyfills/packages/shadydom`][1] 🚨 2 | 3 | The [`webcomponents/template`][2] repo has been migrated to [`packages/shadydom`][1] folder of the [`webcomponents/polyfills`][3] 🚝 *monorepo*. 4 | 5 | We are *actively* working on migrating open Issues and PRs to the new repo. New Issues and PRs should be filed at [`webcomponents/polyfills`][3]. 6 | 7 | [1]: https://github.com/webcomponents/polyfills/tree/master/packages/shadydom 8 | [2]: https://github.com/webcomponents/shadydom 9 | [3]: https://github.com/webcomponents/polyfills 10 | -------------------------------------------------------------------------------- /externs/shadydom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Externs for closure compiler 3 | * @externs 4 | */ 5 | 6 | /** 7 | * Block renaming of properties added to Node to 8 | * prevent conflicts with other closure-compiler code. 9 | * @type {Object} 10 | */ 11 | EventTarget.prototype.__handlers; 12 | 13 | /** @type {Object} */ 14 | Node.prototype.__shady; 15 | 16 | /** @interface */ 17 | function IWrapper() {} 18 | 19 | /** @type {Object} */ 20 | IWrapper.prototype._activeElement; 21 | 22 | // NOTE: For some reason, Closure likes to remove focus() from the IWrapper 23 | // class. Not yet clear why focus() is affected and not any other methods (e.g. 24 | // blur). 25 | IWrapper.prototype.focus = function() {}; 26 | 27 | /** @type {!boolean|undefined} */ 28 | Event.prototype.__composed; 29 | 30 | /** @type {!boolean|undefined} */ 31 | Event.prototype.__immediatePropagationStopped; 32 | 33 | /** @type {!Node|undefined} */ 34 | Event.prototype.__relatedTarget; 35 | 36 | /** @type {!Array|undefined} */ 37 | Event.prototype.__composedPath; 38 | 39 | /** @type {!Array|undefined} */ 40 | Event.prototype.__relatedTargetComposedPath; 41 | 42 | /** 43 | * Prevent renaming of this method on ShadyRoot for testing and debugging. 44 | */ 45 | ShadowRoot.prototype._renderSelf = function(){}; 46 | 47 | // Prevent renaming of properties used by Polymer templates with 48 | // shadyUpgradeFragment optimization 49 | /** @type {!Object} */ 50 | DocumentFragment.prototype.$; 51 | /** @type {boolean} */ 52 | DocumentFragment.prototype.__noInsertionPoint; 53 | /** @type {!Array} */ 54 | DocumentFragment.prototype.nodeList; 55 | /** @type {!Object} */ 56 | DocumentFragment.prototype.templateInfo; 57 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | 'use strict'; 12 | 13 | /* eslint-env node */ 14 | /* eslint-disable no-console */ 15 | 16 | let gulp = require('gulp'); 17 | let compilerPackage = require('google-closure-compiler'); 18 | let sourcemaps = require('gulp-sourcemaps'); 19 | let closureCompiler = compilerPackage.gulp(); 20 | const size = require('gulp-size'); 21 | const rename = require('gulp-rename'); 22 | const rollup = require('gulp-rollup'); 23 | 24 | gulp.task('default', () => { 25 | return gulp.src('./src/**/*.js', {base: '.'}) 26 | .pipe(sourcemaps.init()) 27 | .pipe(closureCompiler({ 28 | compilation_level: 'ADVANCED', 29 | language_in: 'ES6_STRICT', 30 | language_out: 'ES5_STRICT', 31 | isolation_mode: 'IIFE', 32 | assume_function_wrapper: true, 33 | js_output_file: 'shadydom.min.js', 34 | warning_level: 'VERBOSE', 35 | rewrite_polyfills: false, 36 | externs: 'externs/shadydom.js' 37 | })) 38 | .pipe(size({showFiles: true, showTotal: false, gzip: true})) 39 | .pipe(sourcemaps.write('.')) 40 | .pipe(gulp.dest('./')) 41 | }); 42 | 43 | gulp.task('debug', () => { 44 | 45 | const entry = `./src/shadydom.js`; 46 | const fileName = 'shadydom.min'; 47 | 48 | const options = { 49 | input: entry, 50 | output: { 51 | format: 'iife', 52 | name: 'shadydom' 53 | }, 54 | allowRealFiles: true, 55 | rollup: require('rollup') 56 | }; 57 | 58 | return gulp.src(entry) 59 | .pipe(rollup(options)) 60 | .pipe(rename(`${fileName}.js`)) 61 | .pipe(gulp.dest('.')) 62 | }); 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webcomponents/shadydom", 3 | "version": "1.6.0", 4 | "description": "Shadow DOM polyfill", 5 | "main": "shadydom.min.js", 6 | "keywords": [ 7 | "shady-dom", 8 | "shadydom", 9 | "shadow-dom", 10 | "shadowdom", 11 | "web-components", 12 | "webcomponents", 13 | "polyfill", 14 | "shim" 15 | ], 16 | "directories": { 17 | "test": "tests" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/webcomponents/shadydom.git" 22 | }, 23 | "author": "The Polymer Authors", 24 | "license": "BSD-3-Clause", 25 | "bugs": { 26 | "url": "https://github.com/webcomponents/shadydom/issues" 27 | }, 28 | "scripts": { 29 | "build": "gulp", 30 | "test": "npm run lint && npm run build && wct", 31 | "debug": "gulp debug", 32 | "lint": "eslint src", 33 | "regen-package-lock": "rm -rf node_modules package-lock.json; npm install", 34 | "prepack": "npm run build" 35 | }, 36 | "files": [ 37 | "shadydom.min.js*", 38 | "src/**/*.js", 39 | "externs/**/*.js", 40 | "LICENSE.md", 41 | "README.md" 42 | ], 43 | "homepage": "https://webcomponents.org/polyfills", 44 | "devDependencies": { 45 | "@webcomponents/custom-elements": "^1.2.3", 46 | "@webcomponents/shadycss": "^1.7.1", 47 | "@webcomponents/template": "^1.3.1", 48 | "@webcomponents/webcomponents-platform": "^1.0.0", 49 | "eslint": "^4.19.1", 50 | "eslint-plugin-html": "^4.0.5", 51 | "google-closure-compiler": "^20190121.0.0", 52 | "gulp": "^4.0.0", 53 | "gulp-rename": "^1.4.0", 54 | "gulp-rollup": "^2.16.2", 55 | "gulp-size": "^3.0.0", 56 | "gulp-sourcemaps": "^2.6.4", 57 | "promise-polyfill": "^8.0.0", 58 | "rollup": "^0.62.0", 59 | "wct-browser-legacy": "^1.0.1", 60 | "web-component-tester": "^6.9.0" 61 | }, 62 | "publishConfig": { 63 | "access": "public" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | input: 'src/shadydom.js', 3 | output: { file: 'shadydom.min.js', format: 'iife', sourcemap: true } 4 | }; 5 | -------------------------------------------------------------------------------- /src/array-splice.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | function newSplice(index, removed, addedCount) { 12 | return { 13 | index: index, 14 | removed: removed, 15 | addedCount: addedCount 16 | }; 17 | } 18 | 19 | const EDIT_LEAVE = 0; 20 | const EDIT_UPDATE = 1; 21 | const EDIT_ADD = 2; 22 | const EDIT_DELETE = 3; 23 | 24 | // Note: This function is *based* on the computation of the Levenshtein 25 | // "edit" distance. The one change is that "updates" are treated as two 26 | // edits - not one. With Array splices, an update is really a delete 27 | // followed by an add. By retaining this, we optimize for "keeping" the 28 | // maximum array items in the original array. For example: 29 | // 30 | // 'xxxx123' -> '123yyyy' 31 | // 32 | // With 1-edit updates, the shortest path would be just to update all seven 33 | // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This 34 | // leaves the substring '123' intact. 35 | function calcEditDistances(current, currentStart, currentEnd, 36 | old, oldStart, oldEnd) { 37 | // "Deletion" columns 38 | let rowCount = oldEnd - oldStart + 1; 39 | let columnCount = currentEnd - currentStart + 1; 40 | let distances = new Array(rowCount); 41 | 42 | // "Addition" rows. Initialize null column. 43 | for (let i = 0; i < rowCount; i++) { 44 | distances[i] = new Array(columnCount); 45 | distances[i][0] = i; 46 | } 47 | 48 | // Initialize null row 49 | for (let j = 0; j < columnCount; j++) 50 | distances[0][j] = j; 51 | 52 | for (let i = 1; i < rowCount; i++) { 53 | for (let j = 1; j < columnCount; j++) { 54 | if (equals(current[currentStart + j - 1], old[oldStart + i - 1])) 55 | distances[i][j] = distances[i - 1][j - 1]; 56 | else { 57 | let north = distances[i - 1][j] + 1; 58 | let west = distances[i][j - 1] + 1; 59 | distances[i][j] = north < west ? north : west; 60 | } 61 | } 62 | } 63 | 64 | return distances; 65 | } 66 | 67 | // This starts at the final weight, and walks "backward" by finding 68 | // the minimum previous weight recursively until the origin of the weight 69 | // matrix. 70 | function spliceOperationsFromEditDistances(distances) { 71 | let i = distances.length - 1; 72 | let j = distances[0].length - 1; 73 | let current = distances[i][j]; 74 | let edits = []; 75 | while (i > 0 || j > 0) { 76 | if (i == 0) { 77 | edits.push(EDIT_ADD); 78 | j--; 79 | continue; 80 | } 81 | if (j == 0) { 82 | edits.push(EDIT_DELETE); 83 | i--; 84 | continue; 85 | } 86 | let northWest = distances[i - 1][j - 1]; 87 | let west = distances[i - 1][j]; 88 | let north = distances[i][j - 1]; 89 | 90 | let min; 91 | if (west < north) 92 | min = west < northWest ? west : northWest; 93 | else 94 | min = north < northWest ? north : northWest; 95 | 96 | if (min == northWest) { 97 | if (northWest == current) { 98 | edits.push(EDIT_LEAVE); 99 | } else { 100 | edits.push(EDIT_UPDATE); 101 | current = northWest; 102 | } 103 | i--; 104 | j--; 105 | } else if (min == west) { 106 | edits.push(EDIT_DELETE); 107 | i--; 108 | current = west; 109 | } else { 110 | edits.push(EDIT_ADD); 111 | j--; 112 | current = north; 113 | } 114 | } 115 | 116 | edits.reverse(); 117 | return edits; 118 | } 119 | 120 | /** 121 | * Splice Projection functions: 122 | * 123 | * A splice map is a representation of how a previous array of items 124 | * was transformed into a new array of items. Conceptually it is a list of 125 | * tuples of 126 | * 127 | * 128 | * 129 | * which are kept in ascending index order of. The tuple represents that at 130 | * the |index|, |removed| sequence of items were removed, and counting forward 131 | * from |index|, |addedCount| items were added. 132 | */ 133 | 134 | /** 135 | * Lacking individual splice mutation information, the minimal set of 136 | * splices can be synthesized given the previous state and final state of an 137 | * array. The basic approach is to calculate the edit distance matrix and 138 | * choose the shortest path through it. 139 | * 140 | * Complexity: O(l * p) 141 | * l: The length of the current array 142 | * p: The length of the old array 143 | */ 144 | function calcSplices(current, currentStart, currentEnd, 145 | old, oldStart, oldEnd) { 146 | let prefixCount = 0; 147 | let suffixCount = 0; 148 | let splice; 149 | 150 | let minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); 151 | if (currentStart == 0 && oldStart == 0) 152 | prefixCount = sharedPrefix(current, old, minLength); 153 | 154 | if (currentEnd == current.length && oldEnd == old.length) 155 | suffixCount = sharedSuffix(current, old, minLength - prefixCount); 156 | 157 | currentStart += prefixCount; 158 | oldStart += prefixCount; 159 | currentEnd -= suffixCount; 160 | oldEnd -= suffixCount; 161 | 162 | if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) 163 | return []; 164 | 165 | if (currentStart == currentEnd) { 166 | splice = newSplice(currentStart, [], 0); 167 | while (oldStart < oldEnd) 168 | splice.removed.push(old[oldStart++]); 169 | 170 | return [ splice ]; 171 | } else if (oldStart == oldEnd) 172 | return [ newSplice(currentStart, [], currentEnd - currentStart) ]; 173 | 174 | let ops = spliceOperationsFromEditDistances( 175 | calcEditDistances(current, currentStart, currentEnd, 176 | old, oldStart, oldEnd)); 177 | 178 | splice = undefined; 179 | let splices = []; 180 | let index = currentStart; 181 | let oldIndex = oldStart; 182 | for (let i = 0; i < ops.length; i++) { 183 | switch(ops[i]) { 184 | case EDIT_LEAVE: 185 | if (splice) { 186 | splices.push(splice); 187 | splice = undefined; 188 | } 189 | 190 | index++; 191 | oldIndex++; 192 | break; 193 | case EDIT_UPDATE: 194 | if (!splice) 195 | splice = newSplice(index, [], 0); 196 | 197 | splice.addedCount++; 198 | index++; 199 | 200 | splice.removed.push(old[oldIndex]); 201 | oldIndex++; 202 | break; 203 | case EDIT_ADD: 204 | if (!splice) 205 | splice = newSplice(index, [], 0); 206 | 207 | splice.addedCount++; 208 | index++; 209 | break; 210 | case EDIT_DELETE: 211 | if (!splice) 212 | splice = newSplice(index, [], 0); 213 | 214 | splice.removed.push(old[oldIndex]); 215 | oldIndex++; 216 | break; 217 | } 218 | } 219 | 220 | if (splice) { 221 | splices.push(splice); 222 | } 223 | return splices; 224 | } 225 | 226 | function sharedPrefix(current, old, searchLength) { 227 | for (let i = 0; i < searchLength; i++) 228 | if (!equals(current[i], old[i])) 229 | return i; 230 | return searchLength; 231 | } 232 | 233 | function sharedSuffix(current, old, searchLength) { 234 | let index1 = current.length; 235 | let index2 = old.length; 236 | let count = 0; 237 | while (count < searchLength && equals(current[--index1], old[--index2])) 238 | count++; 239 | 240 | return count; 241 | } 242 | 243 | function equals(currentValue, previousValue) { 244 | return currentValue === previousValue; 245 | } 246 | 247 | export function calculateSplices(current, previous) { 248 | return calcSplices(current, 0, current.length, previous, 0, 249 | previous.length); 250 | } 251 | 252 | -------------------------------------------------------------------------------- /src/flush.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from './utils.js'; 12 | 13 | // render enqueuer/flusher 14 | let flushList = []; 15 | let scheduled; 16 | export function enqueue(callback) { 17 | if (!scheduled) { 18 | scheduled = true; 19 | utils.microtask(flush); 20 | } 21 | flushList.push(callback); 22 | } 23 | 24 | export function flush() { 25 | scheduled = false; 26 | let didFlush = Boolean(flushList.length); 27 | while (flushList.length) { 28 | flushList.shift()(); 29 | } 30 | return didFlush; 31 | } 32 | 33 | flush['list'] = flushList; 34 | -------------------------------------------------------------------------------- /src/innerHTML.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | // Cribbed from ShadowDOM polyfill 12 | // https://github.com/webcomponents/webcomponentsjs/blob/master/src/ShadowDOM/wrappers/HTMLElement.js#L28 13 | ///////////////////////////////////////////////////////////////////////////// 14 | // innerHTML and outerHTML 15 | 16 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString 17 | let escapeAttrRegExp = /[&\u00A0"]/g; 18 | let escapeDataRegExp = /[&\u00A0<>]/g; 19 | 20 | function escapeReplace(c) { 21 | switch (c) { 22 | case '&': 23 | return '&'; 24 | case '<': 25 | return '<'; 26 | case '>': 27 | return '>'; 28 | case '"': 29 | return '"'; 30 | case '\u00A0': 31 | return ' '; 32 | } 33 | } 34 | 35 | function escapeAttr(s) { 36 | return s.replace(escapeAttrRegExp, escapeReplace); 37 | } 38 | 39 | function escapeData(s) { 40 | return s.replace(escapeDataRegExp, escapeReplace); 41 | } 42 | 43 | function makeSet(arr) { 44 | let set = {}; 45 | for (let i = 0; i < arr.length; i++) { 46 | set[arr[i]] = true; 47 | } 48 | return set; 49 | } 50 | 51 | // http://www.whatwg.org/specs/web-apps/current-work/#void-elements 52 | let voidElements = makeSet([ 53 | 'area', 54 | 'base', 55 | 'br', 56 | 'col', 57 | 'command', 58 | 'embed', 59 | 'hr', 60 | 'img', 61 | 'input', 62 | 'keygen', 63 | 'link', 64 | 'meta', 65 | 'param', 66 | 'source', 67 | 'track', 68 | 'wbr' 69 | ]); 70 | 71 | let plaintextParents = makeSet([ 72 | 'style', 73 | 'script', 74 | 'xmp', 75 | 'iframe', 76 | 'noembed', 77 | 'noframes', 78 | 'plaintext', 79 | 'noscript' 80 | ]); 81 | 82 | /** 83 | * @param {Node} node 84 | * @param {Node} parentNode 85 | * @param {Function=} callback 86 | */ 87 | export function getOuterHTML(node, parentNode, callback) { 88 | switch (node.nodeType) { 89 | case Node.ELEMENT_NODE: { 90 | let tagName = node.localName; 91 | let s = '<' + tagName; 92 | let attrs = node.attributes; 93 | for (let i = 0, attr; (attr = attrs[i]); i++) { 94 | s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; 95 | } 96 | s += '>'; 97 | if (voidElements[tagName]) { 98 | return s; 99 | } 100 | return s + getInnerHTML(node, callback) + ''; 101 | } 102 | case Node.TEXT_NODE: { 103 | let data = /** @type {Text} */ (node).data; 104 | if (parentNode && plaintextParents[parentNode.localName]) { 105 | return data; 106 | } 107 | return escapeData(data); 108 | } 109 | case Node.COMMENT_NODE: { 110 | return ''; 111 | } 112 | default: { 113 | window.console.error(node); 114 | throw new Error('not implemented'); 115 | } 116 | } 117 | } 118 | 119 | /** 120 | * @param {Node} node 121 | * @param {Function=} callback 122 | */ 123 | export function getInnerHTML(node, callback) { 124 | if (node.localName === 'template') { 125 | node = /** @type {HTMLTemplateElement} */ (node).content; 126 | } 127 | let s = ''; 128 | let c$ = callback ? callback(node) : node.childNodes; 129 | for (let i=0, l=c$.length, child; (i node 21 | nodeData.previousSibling = ref_node ? ref_nodeData.previousSibling : 22 | container[utils.SHADY_PREFIX + 'lastChild']; 23 | let psd = shadyDataForNode(nodeData.previousSibling); 24 | if (psd) { 25 | psd.nextSibling = node; 26 | } 27 | // update node <-> ref_node 28 | let nsd = shadyDataForNode(nodeData.nextSibling = ref_node); 29 | if (nsd) { 30 | nsd.previousSibling = node; 31 | } 32 | // update node <-> container 33 | nodeData.parentNode = container; 34 | if (ref_node) { 35 | if (ref_node === containerData.firstChild) { 36 | containerData.firstChild = node; 37 | } 38 | } else { 39 | containerData.lastChild = node; 40 | if (!containerData.firstChild) { 41 | containerData.firstChild = node; 42 | } 43 | } 44 | // remove caching of childNodes 45 | containerData.childNodes = null; 46 | } 47 | 48 | export const recordInsertBefore = (node, container, ref_node) => { 49 | patchInsideElementAccessors(container); 50 | const containerData = ensureShadyDataForNode(container); 51 | if (containerData.firstChild !== undefined) { 52 | containerData.childNodes = null; 53 | } 54 | // handle document fragments 55 | if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { 56 | // Note, documentFragments should not have logical DOM so there's 57 | // no need update that. It is possible to append a ShadowRoot, but we're 58 | // choosing not to support that. 59 | const first = node[utils.NATIVE_PREFIX + 'firstChild']; 60 | for (let n = first; n; (n = n[utils.NATIVE_PREFIX + 'nextSibling'])) { 61 | linkNode(n, container, containerData, ref_node); 62 | } 63 | } else { 64 | linkNode(node, container, containerData, ref_node); 65 | } 66 | } 67 | 68 | export const recordRemoveChild = (node, container) => { 69 | const nodeData = ensureShadyDataForNode(node); 70 | const containerData = ensureShadyDataForNode(container); 71 | if (node === containerData.firstChild) { 72 | containerData.firstChild = nodeData.nextSibling; 73 | } 74 | if (node === containerData.lastChild) { 75 | containerData.lastChild = nodeData.previousSibling; 76 | } 77 | let p = nodeData.previousSibling; 78 | let n = nodeData.nextSibling; 79 | if (p) { 80 | ensureShadyDataForNode(p).nextSibling = n; 81 | } 82 | if (n) { 83 | ensureShadyDataForNode(n).previousSibling = p; 84 | } 85 | // When an element is removed, logical data is no longer tracked. 86 | // Explicitly set `undefined` here to indicate this. This is disginguished 87 | // from `null` which is set if info is null. 88 | nodeData.parentNode = nodeData.previousSibling = 89 | nodeData.nextSibling = undefined; 90 | if (containerData.childNodes !== undefined) { 91 | // remove caching of childNodes 92 | containerData.childNodes = null; 93 | } 94 | } 95 | 96 | /** 97 | * @param {!Node|DocumentFragment} node 98 | * @param {!Node|DocumentFragment=} adoptedParent 99 | */ 100 | export const recordChildNodes = (node, adoptedParent) => { 101 | const nodeData = ensureShadyDataForNode(node); 102 | if (!adoptedParent && nodeData.firstChild !== undefined) { 103 | return; 104 | } 105 | // remove caching of childNodes 106 | nodeData.childNodes = null; 107 | const first = nodeData.firstChild = node[utils.NATIVE_PREFIX + 'firstChild']; 108 | nodeData.lastChild = node[utils.NATIVE_PREFIX + 'lastChild']; 109 | patchInsideElementAccessors(node); 110 | for (let n = first, previous; n; (n = n[utils.NATIVE_PREFIX + 'nextSibling'])) { 111 | const sd = ensureShadyDataForNode(n); 112 | sd.parentNode = adoptedParent || node; 113 | sd.nextSibling = n[utils.NATIVE_PREFIX + 'nextSibling']; 114 | sd.previousSibling = previous || null; 115 | previous = n; 116 | patchOutsideElementAccessors(n); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/observe-changes.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from './utils.js'; 12 | import {ensureShadyDataForNode} from './shady-data.js'; 13 | 14 | class AsyncObserver { 15 | 16 | constructor() { 17 | this._scheduled = false; 18 | this.addedNodes = []; 19 | this.removedNodes = []; 20 | this.callbacks = new Set(); 21 | } 22 | 23 | schedule() { 24 | if (!this._scheduled) { 25 | this._scheduled = true; 26 | utils.microtask(() => { 27 | this.flush(); 28 | }); 29 | } 30 | } 31 | 32 | flush() { 33 | if (this._scheduled) { 34 | this._scheduled = false; 35 | let mutations = this.takeRecords(); 36 | if (mutations.length) { 37 | this.callbacks.forEach(function(cb) { 38 | cb(mutations); 39 | }); 40 | } 41 | } 42 | } 43 | 44 | takeRecords() { 45 | if (this.addedNodes.length || this.removedNodes.length) { 46 | let mutations = [{ 47 | addedNodes: this.addedNodes, 48 | removedNodes: this.removedNodes 49 | }]; 50 | this.addedNodes = []; 51 | this.removedNodes = []; 52 | return mutations; 53 | } 54 | return []; 55 | } 56 | 57 | } 58 | 59 | // TODO(sorvell): consider instead polyfilling MutationObserver 60 | // directly so that users do not have to fork their code. 61 | // Supporting the entire api may be challenging: e.g. filtering out 62 | // removed nodes in the wrong scope and seeing non-distributing 63 | // subtree child mutations. 64 | export let observeChildren = function(node, callback) { 65 | const sd = ensureShadyDataForNode(node); 66 | if (!sd.observer) { 67 | sd.observer = new AsyncObserver(); 68 | } 69 | sd.observer.callbacks.add(callback); 70 | let observer = sd.observer; 71 | return { 72 | _callback: callback, 73 | _observer: observer, 74 | _node: node, 75 | takeRecords() { 76 | return observer.takeRecords() 77 | } 78 | }; 79 | } 80 | 81 | export let unobserveChildren = function(handle) { 82 | let observer = handle && handle._observer; 83 | if (observer) { 84 | observer.callbacks.delete(handle._callback); 85 | if (!observer.callbacks.size) { 86 | ensureShadyDataForNode(handle._node).observer = null; 87 | } 88 | } 89 | } 90 | 91 | export function filterMutations(mutations, target) { 92 | /** @const {Node} */ 93 | const targetRootNode = target.getRootNode(); 94 | return mutations.map(function(mutation) { 95 | /** @const {boolean} */ 96 | const mutationInScope = (targetRootNode === mutation.target.getRootNode()); 97 | if (mutationInScope && mutation.addedNodes) { 98 | let nodes = Array.from(mutation.addedNodes).filter(function(n) { 99 | return (targetRootNode === n.getRootNode()); 100 | }); 101 | if (nodes.length) { 102 | mutation = Object.create(mutation); 103 | Object.defineProperty(mutation, 'addedNodes', { 104 | value: nodes, 105 | configurable: true 106 | }); 107 | return mutation; 108 | } 109 | } else if (mutationInScope) { 110 | return mutation; 111 | } 112 | }).filter(function(m) { return m}); 113 | } 114 | -------------------------------------------------------------------------------- /src/patch-instances.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from './utils.js'; 12 | import {ensureShadyDataForNode} from './shady-data.js'; 13 | 14 | export const InsideDescriptors = utils.getOwnPropertyDescriptors({ 15 | 16 | /** @this {Node} */ 17 | get childNodes() { 18 | return this[utils.SHADY_PREFIX + 'childNodes']; 19 | }, 20 | 21 | /** @this {Node} */ 22 | get firstChild() { 23 | return this[utils.SHADY_PREFIX + 'firstChild']; 24 | }, 25 | 26 | /** @this {Node} */ 27 | get lastChild() { 28 | return this[utils.SHADY_PREFIX + 'lastChild']; 29 | }, 30 | 31 | /** @this {Node} */ 32 | get childElementCount() { 33 | return this[utils.SHADY_PREFIX + 'childElementCount']; 34 | }, 35 | 36 | /** @this {Node} */ 37 | get children() { 38 | return this[utils.SHADY_PREFIX + 'children']; 39 | }, 40 | 41 | /** @this {Node} */ 42 | get firstElementChild() { 43 | return this[utils.SHADY_PREFIX + 'firstElementChild']; 44 | }, 45 | 46 | /** @this {Node} */ 47 | get lastElementChild() { 48 | return this[utils.SHADY_PREFIX + 'lastElementChild']; 49 | }, 50 | 51 | /** @this {Node} */ 52 | get shadowRoot() { 53 | return this[utils.SHADY_PREFIX + 'shadowRoot']; 54 | }, 55 | 56 | }); 57 | 58 | export const TextContentInnerHTMLDescriptors = utils.getOwnPropertyDescriptors({ 59 | /** @this {Node} */ 60 | get textContent() { 61 | return this[utils.SHADY_PREFIX + 'textContent']; 62 | }, 63 | 64 | /** @this {Node} */ 65 | set textContent(value) { 66 | this[utils.SHADY_PREFIX + 'textContent'] = value; 67 | }, 68 | 69 | /** @this {Node} */ 70 | get innerHTML() { 71 | return this[utils.SHADY_PREFIX + 'innerHTML']; 72 | }, 73 | 74 | /** @this {Node} */ 75 | set innerHTML(value) { 76 | return this[utils.SHADY_PREFIX + 'innerHTML'] = value; 77 | }, 78 | }); 79 | 80 | export const OutsideDescriptors = utils.getOwnPropertyDescriptors({ 81 | 82 | /** @this {Node} */ 83 | get parentElement() { 84 | return this[utils.SHADY_PREFIX + 'parentElement']; 85 | }, 86 | 87 | /** @this {Node} */ 88 | get parentNode() { 89 | return this[utils.SHADY_PREFIX + 'parentNode']; 90 | }, 91 | 92 | /** @this {Node} */ 93 | get nextSibling() { 94 | return this[utils.SHADY_PREFIX + 'nextSibling']; 95 | }, 96 | 97 | /** @this {Node} */ 98 | get previousSibling() { 99 | return this[utils.SHADY_PREFIX + 'previousSibling']; 100 | }, 101 | 102 | /** @this {Node} */ 103 | get nextElementSibling() { 104 | return this[utils.SHADY_PREFIX + 'nextElementSibling']; 105 | }, 106 | 107 | /** @this {Node} */ 108 | get previousElementSibling() { 109 | return this[utils.SHADY_PREFIX + 'previousElementSibling']; 110 | }, 111 | 112 | /** @this {Node} */ 113 | get className() { 114 | return this[utils.SHADY_PREFIX + 'className']; 115 | }, 116 | 117 | /** @this {Node} */ 118 | set className(value) { 119 | return this[utils.SHADY_PREFIX + 'className'] = value; 120 | } 121 | 122 | }); 123 | 124 | const makeNonEnumerable = (descriptors) => { 125 | for (let prop in descriptors) { 126 | const descriptor = descriptors[prop]; 127 | // NOTE, the only known reason the descriptor wouldn't exist here is 128 | // if someone has patched `Object.getOwnPropertyNames`, but we've seen this 129 | // so this is just to be extra safe. 130 | if (descriptor) { 131 | descriptor.enumerable = false; 132 | } 133 | } 134 | } 135 | 136 | makeNonEnumerable(InsideDescriptors); 137 | makeNonEnumerable(TextContentInnerHTMLDescriptors); 138 | makeNonEnumerable(OutsideDescriptors); 139 | 140 | const noInstancePatching = utils.settings.hasDescriptors || utils.settings.noPatch; 141 | 142 | // ensure an element has patched "outside" accessors; no-op when not needed 143 | export let patchOutsideElementAccessors = noInstancePatching ? 144 | function() {} : function(element) { 145 | const sd = ensureShadyDataForNode(element); 146 | if (!sd.__outsideAccessors) { 147 | sd.__outsideAccessors = true; 148 | utils.patchProperties(element, OutsideDescriptors); 149 | } 150 | } 151 | 152 | // ensure an element has patched "inside" accessors; no-op when not needed 153 | export let patchInsideElementAccessors = noInstancePatching ? 154 | function() {} : function(element) { 155 | const sd = ensureShadyDataForNode(element); 156 | if (!sd.__insideAccessors) { 157 | sd.__insideAccessors = true; 158 | utils.patchProperties(element, InsideDescriptors); 159 | // NOTE: There are compatibility issues with patches for `textContent` 160 | // and `innerHTML` between CE and SD. Since SD patches are applied 161 | // via `ShadyDOM.patch` and CE patches are applied as the tree is walked, 162 | // SD patches overwrite CE patches. 163 | // * When SD is in patching mode, SD calls through to native 164 | // methods not patched by CE (since SD is at the bottom) and CE does not 165 | // upgrade, connect, or disconnect elements. Therefore do *not patch* 166 | // these accessors in this case. 167 | // * When SD is in `noPatch` mode, the SD patches call through to 168 | // "native" methods that are patched by CE (since CE is at the bottom). 169 | // Therefore continue to patch in this case. 170 | // If customElements is not loaded, then these accessors should be 171 | // patched so they work correctly. 172 | if (!window['customElements'] || utils.settings.noPatch) { 173 | utils.patchProperties(element, TextContentInnerHTMLDescriptors); 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /src/patch-native.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | import * as utils from './utils.js'; 11 | import {patchProperties} from './utils.js'; 12 | import {getInnerHTML} from './innerHTML.js'; 13 | 14 | const hasDescriptors = utils.settings.hasDescriptors; 15 | export const NATIVE_PREFIX = utils.NATIVE_PREFIX; 16 | 17 | // Object on which raw native methods are stored. 18 | // e.g. `nativeMethods.querySelector.call(node, selector)` 19 | // same as `node.querySelector(selector)` 20 | export const nativeMethods = { 21 | /** @this {Element} */ 22 | querySelector(selector) { 23 | return this[NATIVE_PREFIX + 'querySelector'](selector); 24 | }, 25 | /** @this {Element} */ 26 | querySelectorAll(selector) { 27 | return this[NATIVE_PREFIX + 'querySelectorAll'](selector); 28 | } 29 | }; 30 | // Object on which raw native accessors are available via `accessorName(node)`. 31 | // e.g. `nativeTree.firstChild(node)` 32 | // same as `node.firstChild` 33 | export const nativeTree = {}; 34 | 35 | const installNativeAccessor = (name) => { 36 | nativeTree[name] = (node) => node[NATIVE_PREFIX + name]; 37 | } 38 | 39 | const installNativeMethod = (name, fn) => { 40 | if (!nativeMethods[name]) { 41 | nativeMethods[name] = fn; 42 | } 43 | } 44 | 45 | 46 | const defineNativeAccessors = (proto, descriptors) => { 47 | patchProperties(proto, descriptors, NATIVE_PREFIX); 48 | // make native accessors available to users 49 | for (let prop in descriptors) { 50 | installNativeAccessor(prop); 51 | } 52 | } 53 | 54 | const copyProperties = (proto, list = []) => { 55 | for (let i = 0; i < list.length; i++) { 56 | const name = list[i]; 57 | const descriptor = Object.getOwnPropertyDescriptor(proto, name); 58 | if (descriptor) { 59 | Object.defineProperty(proto, NATIVE_PREFIX + name, descriptor); 60 | // make native methods/accessors available to users 61 | if (descriptor.value) { 62 | installNativeMethod(name, descriptor.value); 63 | } else { 64 | installNativeAccessor(name); 65 | } 66 | } 67 | } 68 | } 69 | 70 | /** @type {!TreeWalker} */ 71 | const nodeWalker = document.createTreeWalker(document, NodeFilter.SHOW_ALL, 72 | null, false); 73 | 74 | /** @type {!TreeWalker} */ 75 | const elementWalker = document.createTreeWalker(document, NodeFilter.SHOW_ELEMENT, 76 | null, false); 77 | 78 | /** @type {!Document} */ 79 | const inertDoc = document.implementation.createHTMLDocument('inert'); 80 | 81 | const clearNode = node => { 82 | let firstChild; 83 | while ((firstChild = node[NATIVE_PREFIX + 'firstChild'])) { 84 | node[NATIVE_PREFIX + 'removeChild'](firstChild); 85 | } 86 | } 87 | 88 | const ParentNodeAccessors = [ 89 | 'firstElementChild', 90 | 'lastElementChild', 91 | 'children', 92 | 'childElementCount', 93 | ]; 94 | 95 | const ParentNodeMethods = [ 96 | 'querySelector', 97 | 'querySelectorAll' 98 | // 'append', 'prepend' 99 | ]; 100 | 101 | export const addNativePrefixedProperties = () => { 102 | 103 | // EventTarget 104 | const eventProps = [ 105 | 'dispatchEvent', 106 | 'addEventListener', 107 | 'removeEventListener' 108 | ]; 109 | if (window.EventTarget) { 110 | copyProperties(window.EventTarget.prototype, eventProps); 111 | } else { 112 | copyProperties(Node.prototype, eventProps); 113 | copyProperties(Window.prototype, eventProps); 114 | } 115 | 116 | 117 | // Node 118 | if (hasDescriptors) { 119 | copyProperties(Node.prototype, [ 120 | 'parentNode', 121 | 'firstChild', 122 | 'lastChild', 123 | 'previousSibling', 124 | 'nextSibling', 125 | 'childNodes', 126 | 'parentElement', 127 | 'textContent', 128 | ]); 129 | } else { 130 | defineNativeAccessors(Node.prototype, { 131 | parentNode: { 132 | /** @this {Node} */ 133 | get() { 134 | nodeWalker.currentNode = this; 135 | return nodeWalker.parentNode(); 136 | } 137 | }, 138 | firstChild: { 139 | /** @this {Node} */ 140 | get() { 141 | nodeWalker.currentNode = this; 142 | return nodeWalker.firstChild(); 143 | } 144 | }, 145 | lastChild: { 146 | /** @this {Node} */ 147 | get() { 148 | nodeWalker.currentNode = this; 149 | return nodeWalker.lastChild(); 150 | } 151 | 152 | }, 153 | previousSibling: { 154 | /** @this {Node} */ 155 | get() { 156 | nodeWalker.currentNode = this; 157 | return nodeWalker.previousSibling(); 158 | } 159 | }, 160 | nextSibling: { 161 | /** @this {Node} */ 162 | get() { 163 | nodeWalker.currentNode = this; 164 | return nodeWalker.nextSibling(); 165 | } 166 | }, 167 | // TODO(sorvell): make this a NodeList or whatever 168 | childNodes: { 169 | /** @this {Node} */ 170 | get() { 171 | const nodes = []; 172 | nodeWalker.currentNode = this; 173 | let n = nodeWalker.firstChild(); 174 | while (n) { 175 | nodes.push(n); 176 | n = nodeWalker.nextSibling(); 177 | } 178 | return nodes; 179 | } 180 | }, 181 | parentElement: { 182 | /** @this {Node} */ 183 | get() { 184 | elementWalker.currentNode = this; 185 | return elementWalker.parentNode(); 186 | } 187 | }, 188 | textContent: { 189 | /** @this {Node} */ 190 | get() { 191 | /* eslint-disable no-case-declarations */ 192 | switch (this.nodeType) { 193 | case Node.ELEMENT_NODE: 194 | case Node.DOCUMENT_FRAGMENT_NODE: 195 | // TODO(sorvell): This cannot be a single TreeWalker that's reused 196 | // at least for Safari 9, but it's unclear why. 197 | const textWalker = document.createTreeWalker(this, NodeFilter.SHOW_TEXT, 198 | null, false); 199 | let content = '', n; 200 | while ( (n = textWalker.nextNode()) ) { 201 | // TODO(sorvell): can't use textContent since we patch it on Node.prototype! 202 | // However, should probably patch it only on element. 203 | content += n.nodeValue; 204 | } 205 | return content; 206 | default: 207 | return this.nodeValue; 208 | } 209 | }, 210 | // Needed on browsers that do not proper accessors (e.g. old versions of Chrome) 211 | /** @this {Node} */ 212 | set(value) { 213 | if (typeof value === 'undefined' || value === null) { 214 | value = '' 215 | } 216 | switch (this.nodeType) { 217 | case Node.ELEMENT_NODE: 218 | case Node.DOCUMENT_FRAGMENT_NODE: 219 | clearNode(this); 220 | // Document fragments must have no childnodes if setting a blank string 221 | if (value.length > 0 || this.nodeType === Node.ELEMENT_NODE) { 222 | // Note: old Chrome versions require 2nd argument here 223 | this[NATIVE_PREFIX + 'insertBefore'](document.createTextNode(value), undefined); 224 | } 225 | break; 226 | default: 227 | // TODO(sorvell): can't do this if patch nodeValue. 228 | this.nodeValue = value; 229 | break; 230 | } 231 | } 232 | } 233 | }); 234 | } 235 | 236 | copyProperties(Node.prototype, [ 237 | 'appendChild', 238 | 'insertBefore', 239 | 'removeChild', 240 | 'replaceChild', 241 | 'cloneNode', 242 | 'contains' 243 | ]); 244 | 245 | // NOTE, on some browsers IE 11 / Edge 15 some properties are incorrectly on HTMLElement 246 | copyProperties(HTMLElement.prototype, [ 247 | 'parentElement', 248 | 'contains' 249 | ]); 250 | 251 | const ParentNodeWalkerDescriptors = { 252 | firstElementChild: { 253 | /** @this {ParentNode} */ 254 | get() { 255 | elementWalker.currentNode = this; 256 | return elementWalker.firstChild(); 257 | } 258 | }, 259 | lastElementChild: { 260 | /** @this {ParentNode} */ 261 | get() { 262 | elementWalker.currentNode = this; 263 | return elementWalker.lastChild(); 264 | } 265 | }, 266 | children: { 267 | /** @this {ParentNode} */ 268 | get() { 269 | let nodes = []; 270 | elementWalker.currentNode = this; 271 | let n = elementWalker.firstChild(); 272 | while (n) { 273 | nodes.push(n); 274 | n = elementWalker.nextSibling(); 275 | } 276 | return utils.createPolyfilledHTMLCollection(nodes); 277 | } 278 | }, 279 | childElementCount: { 280 | /** @this {ParentNode} */ 281 | get() { 282 | if (this.children) { 283 | return this.children.length; 284 | } 285 | return 0; 286 | } 287 | } 288 | }; 289 | 290 | // Element 291 | if (hasDescriptors) { 292 | copyProperties(Element.prototype, ParentNodeAccessors); 293 | 294 | copyProperties(Element.prototype, [ 295 | 'previousElementSibling', 296 | 'nextElementSibling', 297 | 'innerHTML', 298 | 'className' 299 | ]); 300 | 301 | // NOTE, on some browsers IE 11 / Edge 15 some properties are incorrectly on HTMLElement 302 | copyProperties(HTMLElement.prototype, [ 303 | 'children', 304 | 'innerHTML', 305 | 'className' 306 | ]); 307 | } else { 308 | defineNativeAccessors(Element.prototype, ParentNodeWalkerDescriptors); 309 | defineNativeAccessors(Element.prototype, { 310 | previousElementSibling: { 311 | /** @this {Element} */ 312 | get() { 313 | elementWalker.currentNode = this; 314 | return elementWalker.previousSibling(); 315 | } 316 | }, 317 | nextElementSibling: { 318 | /** @this {Element} */ 319 | get() { 320 | elementWalker.currentNode = this; 321 | return elementWalker.nextSibling(); 322 | } 323 | }, 324 | innerHTML: { 325 | /** @this {Element} */ 326 | get() { 327 | return getInnerHTML(this, utils.nativeChildNodesArray); 328 | }, 329 | // Needed on browsers that do not proper accessors (e.g. old versions of Chrome) 330 | /** @this {Element} */ 331 | set(value) { 332 | const content = this.localName === 'template' ? 333 | /** @type {HTMLTemplateElement} */(this).content : this; 334 | clearNode(content); 335 | const containerName = this.localName || 'div'; 336 | let htmlContainer; 337 | if (!this.namespaceURI || this.namespaceURI === inertDoc.namespaceURI) { 338 | htmlContainer = inertDoc.createElement(containerName); 339 | } else { 340 | htmlContainer = inertDoc.createElementNS(this.namespaceURI, containerName); 341 | } 342 | htmlContainer.innerHTML = value; 343 | const newContent = this.localName === 'template' ? 344 | /** @type {HTMLTemplateElement} */(htmlContainer).content : htmlContainer; 345 | let firstChild; 346 | while ((firstChild = newContent[NATIVE_PREFIX + 'firstChild'])) { 347 | // Note: old Chrome versions require 2nd argument here 348 | content[NATIVE_PREFIX + 'insertBefore'](firstChild, undefined); 349 | } 350 | } 351 | }, 352 | className: { 353 | /** @this {Element} */ 354 | get() { 355 | return this.getAttribute('class') || ''; 356 | }, 357 | /** @this {Element} */ 358 | set(value) { 359 | this.setAttribute('class', value); 360 | } 361 | } 362 | }); 363 | } 364 | 365 | copyProperties(Element.prototype, [ 366 | 'setAttribute', 367 | 'getAttribute', 368 | 'hasAttribute', 369 | 'removeAttribute', 370 | // on older Safari, these are on Element. 371 | 'focus', 372 | 'blur', 373 | ]); 374 | copyProperties(Element.prototype, ParentNodeMethods); 375 | 376 | // HTMLElement 377 | copyProperties(HTMLElement.prototype, [ 378 | 'focus', 379 | 'blur' 380 | ]); 381 | 382 | // HTMLTemplateElement 383 | if (window.HTMLTemplateElement) { 384 | copyProperties(window.HTMLTemplateElement.prototype, ['innerHTML']); 385 | } 386 | 387 | // DocumentFragment 388 | if (hasDescriptors) { 389 | // NOTE, IE 11 does not have on DocumentFragment 390 | // firstElementChild 391 | // lastElementChild 392 | copyProperties(DocumentFragment.prototype, ParentNodeAccessors); 393 | } else { 394 | defineNativeAccessors(DocumentFragment.prototype, ParentNodeWalkerDescriptors); 395 | } 396 | 397 | copyProperties(DocumentFragment.prototype, ParentNodeMethods); 398 | 399 | // Document 400 | if (hasDescriptors) { 401 | copyProperties(Document.prototype, ParentNodeAccessors); 402 | copyProperties(Document.prototype, [ 403 | 'activeElement' 404 | ]); 405 | } else { 406 | defineNativeAccessors(Document.prototype, ParentNodeWalkerDescriptors); 407 | } 408 | 409 | copyProperties(Document.prototype, [ 410 | 'importNode', 411 | 'getElementById' 412 | ]); 413 | copyProperties(Document.prototype, ParentNodeMethods); 414 | 415 | }; 416 | -------------------------------------------------------------------------------- /src/patch-prototypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from './utils.js'; 12 | import {EventTargetPatches} from './patches/EventTarget.js'; 13 | import {NodePatches} from './patches/Node.js'; 14 | import {SlotablePatches} from './patches/Slotable.js'; 15 | import {ParentNodePatches, ParentNodeDocumentOrFragmentPatches} from './patches/ParentNode.js'; 16 | import {ElementPatches} from './patches/Element.js'; 17 | import {ElementOrShadowRootPatches} from './patches/ElementOrShadowRoot.js'; 18 | import {HTMLElementPatches} from './patches/HTMLElement.js'; 19 | import {SlotPatches} from './patches/Slot.js'; 20 | import {DocumentOrFragmentPatches} from './patches/DocumentOrFragment.js'; 21 | import {DocumentOrShadowRootPatches} from './patches/DocumentOrShadowRoot.js'; 22 | import {DocumentPatches} from './patches/Document.js'; 23 | import {WindowPatches} from './patches/Window.js'; 24 | 25 | // Some browsers (IE/Edge) have non-standard HTMLElement accessors. 26 | const NonStandardHTMLElement = {}; 27 | 28 | if (Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'parentElement')) { 29 | NonStandardHTMLElement.parentElement = NodePatches.parentElement; 30 | } 31 | 32 | if (Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'contains')) { 33 | NonStandardHTMLElement.contains = NodePatches.contains; 34 | } 35 | 36 | if (Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'children')) { 37 | NonStandardHTMLElement.children = ParentNodePatches.children; 38 | } 39 | 40 | if (Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerHTML')) { 41 | NonStandardHTMLElement.innerHTML = ElementOrShadowRootPatches.innerHTML; 42 | } 43 | 44 | if (Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'className')) { 45 | NonStandardHTMLElement.className = ElementPatches.className; 46 | } 47 | 48 | // Avoid patching `innerHTML` if it does not exist on Element (IE) 49 | // and we can patch accessors (hasDescriptors). 50 | const ElementShouldHaveInnerHTML = !utils.settings.hasDescriptors || 'innerHTML' in Element.prototype; 51 | 52 | // setup patching 53 | const patchMap = { 54 | EventTarget: [EventTargetPatches], 55 | Node: [NodePatches, !window.EventTarget ? EventTargetPatches : null], 56 | Text: [SlotablePatches], 57 | Element: [ElementPatches, ParentNodePatches, SlotablePatches, 58 | ElementShouldHaveInnerHTML ? ElementOrShadowRootPatches : null, 59 | !window.HTMLSlotElement ? SlotPatches : null], 60 | HTMLElement: [HTMLElementPatches, NonStandardHTMLElement], 61 | HTMLSlotElement: [SlotPatches], 62 | DocumentFragment: [ParentNodeDocumentOrFragmentPatches, DocumentOrFragmentPatches], 63 | Document: [DocumentPatches, ParentNodeDocumentOrFragmentPatches, DocumentOrFragmentPatches, DocumentOrShadowRootPatches], 64 | Window: [WindowPatches] 65 | } 66 | 67 | const getPatchPrototype = (name) => window[name] && window[name].prototype; 68 | 69 | // Note, must avoid patching accessors on prototypes when descriptors are not correct 70 | // because the CustomElements polyfill checks if these exist before patching instances. 71 | // CustomElements polyfill *only* cares about these accessors. 72 | const disallowedNativePatches = utils.settings.hasDescriptors ? null : ['innerHTML', 'textContent']; 73 | 74 | /** @param {string=} prefix */ 75 | export const applyPatches = (prefix) => { 76 | const disallowed = prefix ? null : disallowedNativePatches; 77 | for (let p in patchMap) { 78 | const proto = getPatchPrototype(p); 79 | patchMap[p].forEach(patch => proto && patch && 80 | utils.patchProperties(proto, patch, prefix, disallowed)); 81 | } 82 | } 83 | 84 | export const addShadyPrefixedProperties = () => { 85 | // perform shady patches 86 | applyPatches(utils.SHADY_PREFIX); 87 | 88 | // install `_activeElement` because some browsers (older Chrome/Safari) do not have 89 | // a 'configurable' `activeElement` accesssor. 90 | const descriptor = DocumentOrShadowRootPatches.activeElement; 91 | Object.defineProperty(document, '_activeElement', descriptor); 92 | 93 | // On Window, we're patching `addEventListener` which is a weird auto-bound 94 | // property that is not directly on the Window prototype. 95 | utils.patchProperties(Window.prototype, WindowPatches, utils.SHADY_PREFIX); 96 | }; 97 | -------------------------------------------------------------------------------- /src/patch-shadyRoot.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from './utils.js'; 12 | import {NodePatches} from './patches/Node.js'; 13 | import {OutsideDescriptors, InsideDescriptors, TextContentInnerHTMLDescriptors} from './patch-instances.js'; 14 | import {ParentNodePatches} from './patches/ParentNode.js'; 15 | import {DocumentOrFragmentPatches} from './patches/DocumentOrFragment.js'; 16 | import {DocumentOrShadowRootPatches} from './patches/DocumentOrShadowRoot.js'; 17 | import {ElementOrShadowRootPatches} from './patches/ElementOrShadowRoot.js'; 18 | import {ShadowRootPatches} from './patches/ShadowRoot.js'; 19 | 20 | /** 21 | * @param {!Object} proto 22 | * @param {string=} prefix 23 | */ 24 | const patchShadyAccessors = (proto, prefix) => { 25 | utils.patchProperties(proto, ShadowRootPatches, prefix); 26 | utils.patchProperties(proto, DocumentOrShadowRootPatches, prefix); 27 | utils.patchProperties(proto, ElementOrShadowRootPatches, prefix); 28 | // We ensure ParentNode accessors since these do not exist in Edge/IE on DocumentFragments. 29 | utils.patchProperties(proto, ParentNodePatches, prefix); 30 | // Ensure `shadowRoot` has basic descriptors when we cannot rely 31 | // on them coming from DocumentFragment. 32 | // 33 | // Case 1, noPatching: Because we want noPatch ShadyRoots to have native property 34 | // names so that they do not have to be wrapped... 35 | // When we do *not* patch Node/DocumentFragment.prototype 36 | // we must manually install those properties on ShadyRoot's prototype. 37 | // Note, it's important to only install these in this mode so as not to stomp 38 | // over CustomElements polyfill's patches on Node/DocumentFragment methods. 39 | if (utils.settings.noPatch && !prefix) { 40 | utils.patchProperties(proto, NodePatches, prefix); 41 | utils.patchProperties(proto, DocumentOrFragmentPatches, prefix); 42 | // Case 2, bad descriptors: Ensure accessors are on ShadowRoot. 43 | // These descriptors are normally used for instance patching but because 44 | // ShadyRoot can always be patched, just do it to the prototype. 45 | } else if (!utils.settings.hasDescriptors) { 46 | utils.patchProperties(proto, OutsideDescriptors); 47 | utils.patchProperties(proto, InsideDescriptors); 48 | utils.patchProperties(proto, TextContentInnerHTMLDescriptors); 49 | } 50 | } 51 | 52 | export const patchShadyRoot = (proto) => { 53 | proto.__proto__ = DocumentFragment.prototype; 54 | 55 | // patch both prefixed and not, even when noPatch == true. 56 | patchShadyAccessors(proto, utils.SHADY_PREFIX); 57 | patchShadyAccessors(proto); 58 | 59 | // Ensure native properties are all safely wrapped since ShadowRoot is not an 60 | // actual DocumentFragment instance. 61 | Object.defineProperties(proto, { 62 | nodeType: { 63 | value: Node.DOCUMENT_FRAGMENT_NODE, 64 | configurable: true 65 | }, 66 | nodeName: { 67 | value: '#document-fragment', 68 | configurable: true 69 | }, 70 | nodeValue: { 71 | value: null, 72 | configurable: true 73 | } 74 | }); 75 | 76 | // make undefined 77 | [ 78 | 'localName', 79 | 'namespaceURI', 80 | 'prefix' 81 | ].forEach((prop) => { 82 | Object.defineProperty(proto, prop, { 83 | value: undefined, 84 | configurable: true 85 | }); 86 | }); 87 | 88 | // defer properties to host 89 | [ 90 | 'ownerDocument', 91 | 'baseURI', 92 | 'isConnected' 93 | ].forEach((prop) => { 94 | Object.defineProperty(proto, prop, { 95 | /** @this {ShadowRoot} */ 96 | get() { 97 | return this.host[prop]; 98 | }, 99 | configurable: true 100 | }); 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /src/patches/Document.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | 13 | const doc = window.document; 14 | 15 | export const DocumentPatches = utils.getOwnPropertyDescriptors({ 16 | 17 | // note: Though not technically correct, we fast path `importNode` 18 | // when called on a node not owned by the main document. 19 | // This allows, for example, elements that cannot 20 | // contain custom elements and are therefore not likely to contain shadowRoots 21 | // to cloned natively. This is a fairly significant performance win. 22 | /** 23 | * @this {Document} 24 | * @param {Node} node 25 | * @param {boolean} deep 26 | */ 27 | importNode(node, deep) { 28 | // A template element normally has no children with shadowRoots, so make 29 | // sure we always make a deep copy to correctly construct the template.content 30 | if (node.ownerDocument !== doc || node.localName === 'template') { 31 | return this[utils.NATIVE_PREFIX + 'importNode'](node, deep); 32 | } 33 | let n = this[utils.NATIVE_PREFIX + 'importNode'](node, false); 34 | if (deep) { 35 | for (let c=node[utils.SHADY_PREFIX + 'firstChild'], nc; c; c = c[utils.SHADY_PREFIX + 'nextSibling']) { 36 | nc = this[utils.SHADY_PREFIX + 'importNode'](c, true); 37 | n[utils.SHADY_PREFIX + 'appendChild'](nc); 38 | } 39 | } 40 | return n; 41 | } 42 | 43 | }); -------------------------------------------------------------------------------- /src/patches/DocumentOrFragment.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | import * as utils from '../utils.js'; 11 | import { query } from './ParentNode.js'; 12 | 13 | export const DocumentOrFragmentPatches = utils.getOwnPropertyDescriptors({ 14 | 15 | /** 16 | * @this {Element} 17 | * @param {string} id 18 | */ 19 | getElementById(id) { 20 | if (id === '') { 21 | return null; 22 | } 23 | let result = query(this, function(n) { 24 | return n.id == id; 25 | }, function(n) { 26 | return Boolean(n); 27 | })[0]; 28 | return result || null; 29 | } 30 | 31 | }); -------------------------------------------------------------------------------- /src/patches/DocumentOrShadowRoot.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | import {ownerShadyRootForNode} from '../attach-shadow.js'; 13 | 14 | function getDocumentActiveElement() { 15 | if (utils.settings.hasDescriptors) { 16 | return document[utils.NATIVE_PREFIX + 'activeElement']; 17 | } else { 18 | return document.activeElement; 19 | } 20 | } 21 | 22 | export const DocumentOrShadowRootPatches = utils.getOwnPropertyDescriptors({ 23 | 24 | /** @this {Document|ShadowRoot} */ 25 | get activeElement() { 26 | let active = getDocumentActiveElement(); 27 | // In IE11, activeElement might be an empty object if the document is 28 | // contained in an iframe. 29 | // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10998788/ 30 | if (!active || !active.nodeType) { 31 | return null; 32 | } 33 | let isShadyRoot = !!(utils.isShadyRoot(this)); 34 | if (this !== document) { 35 | // If this node isn't a document or shady root, then it doesn't have 36 | // an active element. 37 | if (!isShadyRoot) { 38 | return null; 39 | } 40 | // If this shady root's host is the active element or the active 41 | // element is not a descendant of the host (in the composed tree), 42 | // then it doesn't have an active element. 43 | if (this.host === active || 44 | !this.host[utils.NATIVE_PREFIX + 'contains'](active)) { 45 | return null; 46 | } 47 | } 48 | // This node is either the document or a shady root of which the active 49 | // element is a (composed) descendant of its host; iterate upwards to 50 | // find the active element's most shallow host within it. 51 | let activeRoot = ownerShadyRootForNode(active); 52 | while (activeRoot && activeRoot !== this) { 53 | active = activeRoot.host; 54 | activeRoot = ownerShadyRootForNode(active); 55 | } 56 | if (this === document) { 57 | // This node is the document, so activeRoot should be null. 58 | return activeRoot ? null : active; 59 | } else { 60 | // This node is a non-document shady root, and it should be 61 | // activeRoot. 62 | return activeRoot === this ? active : null; 63 | } 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /src/patches/Element.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | import {scopeClassAttribute} from '../style-scoping.js'; 13 | import {shadyDataForNode} from '../shady-data.js'; 14 | import {attachShadow, ownerShadyRootForNode} from '../attach-shadow.js'; 15 | 16 | const doc = window.document; 17 | 18 | /** 19 | * Should be called whenever an attribute changes. If the `slot` attribute 20 | * changes, provokes rendering if necessary. If a `` element's `name` 21 | * attribute changes, updates the root's slot map and renders. 22 | * @param {Node} node 23 | * @param {string} name 24 | */ 25 | function distributeAttributeChange(node, name) { 26 | if (name === 'slot') { 27 | const parent = node[utils.SHADY_PREFIX + 'parentNode']; 28 | if (utils.hasShadowRootWithSlot(parent)) { 29 | shadyDataForNode(parent).root._asyncRender(); 30 | } 31 | } else if (node.localName === 'slot' && name === 'name') { 32 | let root = ownerShadyRootForNode(node); 33 | if (root) { 34 | root._updateSlotName(node); 35 | root._asyncRender(); 36 | } 37 | } 38 | } 39 | 40 | export const ElementPatches = utils.getOwnPropertyDescriptors({ 41 | 42 | /** @this {Element} */ 43 | get previousElementSibling() { 44 | const nodeData = shadyDataForNode(this); 45 | if (nodeData && nodeData.previousSibling !== undefined) { 46 | let n = this[utils.SHADY_PREFIX + 'previousSibling']; 47 | while (n && n.nodeType !== Node.ELEMENT_NODE) { 48 | n = n[utils.SHADY_PREFIX + 'previousSibling']; 49 | } 50 | return n; 51 | } else { 52 | return this[utils.NATIVE_PREFIX + 'previousElementSibling']; 53 | } 54 | }, 55 | 56 | /** @this {Element} */ 57 | get nextElementSibling() { 58 | const nodeData = shadyDataForNode(this); 59 | if (nodeData && nodeData.nextSibling !== undefined) { 60 | let n = this[utils.SHADY_PREFIX + 'nextSibling']; 61 | while (n && n.nodeType !== Node.ELEMENT_NODE) { 62 | n = n[utils.SHADY_PREFIX + 'nextSibling']; 63 | } 64 | return n; 65 | } else { 66 | return this[utils.NATIVE_PREFIX + 'nextElementSibling']; 67 | } 68 | }, 69 | 70 | /** @this {Element} */ 71 | get slot() { 72 | return this.getAttribute('slot'); 73 | }, 74 | 75 | /** @this {Element} */ 76 | set slot(value) { 77 | this[utils.SHADY_PREFIX + 'setAttribute']('slot', value); 78 | }, 79 | 80 | // Note: Can be patched on element prototype on all browsers. 81 | // Must be patched on instance on browsers that support native Shadow DOM 82 | // but do not have builtin accessors (old Chrome). 83 | /** @this {Element} */ 84 | get shadowRoot() { 85 | const nodeData = shadyDataForNode(this); 86 | return nodeData && nodeData.publicRoot || null; 87 | }, 88 | 89 | /** @this {Element} */ 90 | get className() { 91 | return this.getAttribute('class') || ''; 92 | }, 93 | 94 | /** 95 | * @this {Element} 96 | * @param {string} value 97 | */ 98 | set className(value) { 99 | this[utils.SHADY_PREFIX + 'setAttribute']('class', value); 100 | }, 101 | 102 | /** 103 | * @this {Element} 104 | * @param {string} attr 105 | * @param {string} value 106 | */ 107 | setAttribute(attr, value) { 108 | if (this.ownerDocument !== doc) { 109 | this[utils.NATIVE_PREFIX + 'setAttribute'](attr, value); 110 | } else if (!scopeClassAttribute(this, attr, value)) { 111 | this[utils.NATIVE_PREFIX + 'setAttribute'](attr, value); 112 | distributeAttributeChange(this, attr); 113 | } 114 | }, 115 | 116 | /** 117 | * @this {Element} 118 | * @param {string} attr 119 | */ 120 | removeAttribute(attr) { 121 | if (this.ownerDocument !== doc) { 122 | this[utils.NATIVE_PREFIX + 'removeAttribute'](attr); 123 | } else if (!scopeClassAttribute(this, attr, '')) { 124 | this[utils.NATIVE_PREFIX + 'removeAttribute'](attr); 125 | distributeAttributeChange(this, attr); 126 | } else if (this.getAttribute(attr) === '') { 127 | // ensure that "class" attribute is fully removed if ShadyCSS does not keep scoping 128 | this[utils.NATIVE_PREFIX + 'removeAttribute'](attr); 129 | } 130 | }, 131 | 132 | /** 133 | * @this {Element} 134 | * @param {!{mode: string}} options 135 | */ 136 | attachShadow(options) { 137 | return attachShadow(this, options); 138 | } 139 | 140 | }); 141 | -------------------------------------------------------------------------------- /src/patches/ElementOrShadowRoot.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | import {getInnerHTML} from '../innerHTML.js'; 13 | import {clearNode} from './Node.js'; 14 | 15 | /** @type {!Document} */ 16 | const inertDoc = document.implementation.createHTMLDocument('inert'); 17 | 18 | export const ElementOrShadowRootPatches = utils.getOwnPropertyDescriptors({ 19 | 20 | /** @this {Element} */ 21 | get innerHTML() { 22 | if (utils.isTrackingLogicalChildNodes(this)) { 23 | const content = this.localName === 'template' ? 24 | /** @type {HTMLTemplateElement} */(this).content : this; 25 | return getInnerHTML(content, utils.childNodesArray); 26 | } else { 27 | return this[utils.NATIVE_PREFIX + 'innerHTML']; 28 | } 29 | }, 30 | 31 | /** 32 | * @this {Element} 33 | * @param {string} value 34 | */ 35 | set innerHTML(value) { 36 | if (this.localName === 'template') { 37 | this[utils.NATIVE_PREFIX + 'innerHTML'] = value; 38 | } else { 39 | clearNode(this); 40 | const containerName = this.localName || 'div'; 41 | let htmlContainer; 42 | if (!this.namespaceURI || this.namespaceURI === inertDoc.namespaceURI) { 43 | htmlContainer = inertDoc.createElement(containerName); 44 | } else { 45 | htmlContainer = inertDoc.createElementNS(this.namespaceURI, containerName); 46 | } 47 | if (utils.settings.hasDescriptors) { 48 | htmlContainer[utils.NATIVE_PREFIX + 'innerHTML'] = value; 49 | } else { 50 | htmlContainer.innerHTML = value; 51 | } 52 | let firstChild; 53 | while ((firstChild = htmlContainer[utils.SHADY_PREFIX + 'firstChild'])) { 54 | this[utils.SHADY_PREFIX + 'insertBefore'](firstChild); 55 | } 56 | } 57 | } 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /src/patches/EventTarget.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | import {addEventListener, removeEventListener, dispatchEvent} from '../patch-events.js'; 13 | 14 | export const EventTargetPatches = utils.getOwnPropertyDescriptors({ 15 | 16 | dispatchEvent, 17 | 18 | addEventListener, 19 | 20 | removeEventListener 21 | 22 | }); -------------------------------------------------------------------------------- /src/patches/HTMLElement.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | import {eventPropertyNames} from '../patch-events.js'; 13 | import {shadyDataForNode, ensureShadyDataForNode} from '../shady-data.js'; 14 | 15 | export const HTMLElementPatches = utils.getOwnPropertyDescriptors({ 16 | 17 | /** @this {HTMLElement} */ 18 | blur() { 19 | const nodeData = shadyDataForNode(this); 20 | let root = nodeData && nodeData.root; 21 | let shadowActive = root && root.activeElement; 22 | if (shadowActive) { 23 | shadowActive[utils.SHADY_PREFIX + 'blur'](); 24 | } else { 25 | this[utils.NATIVE_PREFIX + 'blur'](); 26 | } 27 | } 28 | 29 | }); 30 | 31 | eventPropertyNames.forEach(property => { 32 | HTMLElementPatches[property] = { 33 | /** @this {HTMLElement} */ 34 | set: function(fn) { 35 | const shadyData = ensureShadyDataForNode(this); 36 | const eventName = property.substring(2); 37 | if (!shadyData.__onCallbackListeners) { 38 | shadyData.__onCallbackListeners = {}; 39 | } 40 | shadyData.__onCallbackListeners[property] && this.removeEventListener(eventName, shadyData.__onCallbackListeners[property]); 41 | this[utils.SHADY_PREFIX + 'addEventListener'](eventName, fn); 42 | shadyData.__onCallbackListeners[property] = fn; 43 | }, 44 | /** @this {HTMLElement} */ 45 | get() { 46 | const shadyData = shadyDataForNode(this); 47 | return shadyData && shadyData.__onCallbackListeners && shadyData.__onCallbackListeners[property]; 48 | }, 49 | configurable: true 50 | }; 51 | }); 52 | 53 | -------------------------------------------------------------------------------- /src/patches/ParentNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | import {shadyDataForNode} from '../shady-data.js'; 13 | 14 | /** 15 | * @param {Node} node 16 | * @param {Function} matcher 17 | * @param {Function=} halter 18 | */ 19 | export function query(node, matcher, halter) { 20 | let list = []; 21 | queryChildNodes(node, matcher, 22 | halter, list); 23 | return list; 24 | } 25 | 26 | function queryChildNodes(parent, matcher, halter, list) { 27 | for (let n = parent[utils.SHADY_PREFIX + 'firstChild']; n; n = n[utils.SHADY_PREFIX + 'nextSibling']) { 28 | if (n.nodeType === Node.ELEMENT_NODE && 29 | queryElement(n, matcher, halter, list)) { 30 | return true; 31 | } 32 | } 33 | } 34 | 35 | function queryElement(node, matcher, halter, list) { 36 | let result = matcher(node); 37 | if (result) { 38 | list.push(node); 39 | } 40 | if (halter && halter(result)) { 41 | return result; 42 | } 43 | queryChildNodes(node, matcher, 44 | halter, list); 45 | } 46 | 47 | // Needed on Element, DocumentFragment, Document 48 | export const ParentNodePatches = utils.getOwnPropertyDescriptors({ 49 | 50 | /** @this {Element} */ 51 | get firstElementChild() { 52 | const nodeData = shadyDataForNode(this); 53 | if (nodeData && nodeData.firstChild !== undefined) { 54 | let n = this[utils.SHADY_PREFIX + 'firstChild']; 55 | while (n && n.nodeType !== Node.ELEMENT_NODE) { 56 | n = n[utils.SHADY_PREFIX + 'nextSibling']; 57 | } 58 | return n; 59 | } else { 60 | return this[utils.NATIVE_PREFIX + 'firstElementChild']; 61 | } 62 | }, 63 | 64 | /** @this {Element} */ 65 | get lastElementChild() { 66 | const nodeData = shadyDataForNode(this); 67 | if (nodeData && nodeData.lastChild !== undefined) { 68 | let n = this[utils.SHADY_PREFIX + 'lastChild']; 69 | while (n && n.nodeType !== Node.ELEMENT_NODE) { 70 | n = n[utils.SHADY_PREFIX + 'previousSibling']; 71 | } 72 | return n; 73 | } else { 74 | return this[utils.NATIVE_PREFIX + 'lastElementChild']; 75 | } 76 | }, 77 | 78 | /** @this {Element} */ 79 | get children() { 80 | if (!utils.isTrackingLogicalChildNodes(this)) { 81 | return this[utils.NATIVE_PREFIX + 'children']; 82 | } 83 | return utils.createPolyfilledHTMLCollection(Array.prototype.filter.call( 84 | utils.childNodesArray(this), (n) => { 85 | return (n.nodeType === Node.ELEMENT_NODE); 86 | })); 87 | }, 88 | 89 | /** @this {Element} */ 90 | get childElementCount() { 91 | let children = this[utils.SHADY_PREFIX + 'children']; 92 | if(children) { 93 | return children.length; 94 | } 95 | return 0; 96 | } 97 | 98 | }); 99 | 100 | export const QueryPatches = utils.getOwnPropertyDescriptors({ 101 | // TODO(sorvell): consider doing native QSA and filtering results. 102 | /** 103 | * @this {Element} 104 | * @param {string} selector 105 | */ 106 | querySelector(selector) { 107 | // match selector and halt on first result. 108 | let result = query(this, function(n) { 109 | return utils.matchesSelector(n, selector); 110 | }, function(n) { 111 | return Boolean(n); 112 | })[0]; 113 | return result || null; 114 | }, 115 | 116 | /** 117 | * @this {Element} 118 | * @param {string} selector 119 | * @param {boolean} useNative 120 | */ 121 | // TODO(sorvell): `useNative` option relies on native querySelectorAll and 122 | // misses distributed nodes, see 123 | // https://github.com/webcomponents/shadydom/pull/210#issuecomment-361435503 124 | querySelectorAll(selector, useNative) { 125 | if (useNative) { 126 | const o = Array.prototype.slice.call(this[utils.NATIVE_PREFIX + 'querySelectorAll'](selector)); 127 | const root = this[utils.SHADY_PREFIX + 'getRootNode'](); 128 | return utils.createPolyfilledHTMLCollection(o.filter(e => e[utils.SHADY_PREFIX + 'getRootNode']() == root)); 129 | } 130 | return utils.createPolyfilledHTMLCollection(query(this, function(n) { 131 | return utils.matchesSelector(n, selector); 132 | })); 133 | } 134 | 135 | }); 136 | 137 | // In preferPerformance mode, create a custom `ParentNodeDocumentOrFragment` 138 | // that optionally does not mixin querySelector/All; this is a performance 139 | // optimization. In noPatch, we need to keep the query patches here in order to 140 | // ensure the query API is available on the wrapper 141 | export const ParentNodeDocumentOrFragmentPatches = 142 | (utils.settings.preferPerformance && !utils.settings.noPatch) ? 143 | Object.assign({}, ParentNodePatches) : ParentNodePatches; 144 | 145 | Object.assign(ParentNodePatches, QueryPatches); -------------------------------------------------------------------------------- /src/patches/ShadowRoot.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | 13 | export const ShadowRootPatches = utils.getOwnPropertyDescriptors({ 14 | 15 | /** 16 | * @this {ShadowRoot} 17 | * @param {string} type 18 | * @param {Function} fn 19 | * @param {Object|boolean=} optionsOrCapture 20 | */ 21 | addEventListener(type, fn, optionsOrCapture) { 22 | if (typeof optionsOrCapture !== 'object') { 23 | optionsOrCapture = { 24 | capture: Boolean(optionsOrCapture) 25 | } 26 | } 27 | // Note, `__shadyTarget` may already be set if an event was added on a child 28 | optionsOrCapture.__shadyTarget = optionsOrCapture.__shadyTarget || this; 29 | this.host[utils.SHADY_PREFIX + 'addEventListener'](type, fn, optionsOrCapture); 30 | }, 31 | 32 | /** 33 | * @this {ShadowRoot} 34 | * @param {string} type 35 | * @param {Function} fn 36 | * @param {Object|boolean=} optionsOrCapture 37 | */ 38 | removeEventListener(type, fn, optionsOrCapture) { 39 | if (typeof optionsOrCapture !== 'object') { 40 | optionsOrCapture = { 41 | capture: Boolean(optionsOrCapture) 42 | } 43 | } 44 | // Note, `__shadyTarget` may already be set if an event was added on a child 45 | optionsOrCapture.__shadyTarget = optionsOrCapture.__shadyTarget || this; 46 | this.host[utils.SHADY_PREFIX + 'removeEventListener'](type, fn, optionsOrCapture); 47 | } 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /src/patches/Slot.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | import {shadyDataForNode} from '../shady-data.js'; 13 | import {addEventListener, removeEventListener} from '../patch-events.js'; 14 | 15 | export const SlotPatches = utils.getOwnPropertyDescriptors({ 16 | 17 | /** 18 | * @this {HTMLSlotElement} 19 | * @param {Object=} options 20 | */ 21 | assignedNodes(options) { 22 | if (this.localName === 'slot') { 23 | // Force any containing shadowRoot to flush so that distribution occurs 24 | // and this node has assignedNodes. 25 | const root = this[utils.SHADY_PREFIX + 'getRootNode'](); 26 | if (root && utils.isShadyRoot(root)) { 27 | root._render(); 28 | } 29 | const nodeData = shadyDataForNode(this); 30 | return nodeData ? 31 | ((options && options.flatten ? nodeData.flattenedNodes : 32 | nodeData.assignedNodes) || []) : 33 | []; 34 | } 35 | }, 36 | 37 | /** 38 | * @this {HTMLSlotElement} 39 | * @param {string} type 40 | * @param {Function} fn 41 | * @param {Object|boolean=} optionsOrCapture 42 | */ 43 | addEventListener(type, fn, optionsOrCapture) { 44 | // NOTE, check if this is a `slot` because these patches are installed on 45 | // Element where browsers don't have `` 46 | if (this.localName !== 'slot' || type === 'slotchange') { 47 | addEventListener.call(this, type, fn, optionsOrCapture); 48 | } else { 49 | if (typeof optionsOrCapture !== 'object') { 50 | optionsOrCapture = { 51 | capture: Boolean(optionsOrCapture) 52 | } 53 | } 54 | const parent = this[utils.SHADY_PREFIX + 'parentNode']; 55 | if (!parent) { 56 | throw new Error('ShadyDOM cannot attach event to slot unless it has a `parentNode`'); 57 | } 58 | optionsOrCapture.__shadyTarget = this; 59 | parent[utils.SHADY_PREFIX + 'addEventListener'](type, fn, optionsOrCapture); 60 | } 61 | }, 62 | 63 | /** 64 | * @this {HTMLSlotElement} 65 | * @param {string} type 66 | * @param {Function} fn 67 | * @param {Object|boolean=} optionsOrCapture 68 | */ 69 | removeEventListener(type, fn, optionsOrCapture) { 70 | // NOTE, check if this is a `slot` because these patches are installed on 71 | // Element where browsers don't have `` 72 | if (this.localName !== 'slot' || type === 'slotchange') { 73 | removeEventListener.call(this, type, fn, optionsOrCapture); 74 | } else { 75 | if (typeof optionsOrCapture !== 'object') { 76 | optionsOrCapture = { 77 | capture: Boolean(optionsOrCapture) 78 | } 79 | } 80 | const parent = this[utils.SHADY_PREFIX + 'parentNode']; 81 | if (!parent) { 82 | throw new Error('ShadyDOM cannot attach event to slot unless it has a `parentNode`'); 83 | } 84 | optionsOrCapture.__shadyTarget = this; 85 | parent[utils.SHADY_PREFIX + 'removeEventListener'](type, fn, optionsOrCapture); 86 | } 87 | } 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /src/patches/Slotable.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from '../utils.js'; 12 | import {shadyDataForNode} from '../shady-data.js'; 13 | 14 | export const SlotablePatches = utils.getOwnPropertyDescriptors({ 15 | 16 | /** @this {Node} */ 17 | get assignedSlot() { 18 | // Force any parent's shadowRoot to flush so that distribution occurs 19 | // and this node has an assignedSlot. 20 | const parent = this[utils.SHADY_PREFIX + 'parentNode']; 21 | const ownerRoot = parent && parent[utils.SHADY_PREFIX + 'shadowRoot']; 22 | if (ownerRoot) { 23 | ownerRoot._render(); 24 | } 25 | const nodeData = shadyDataForNode(this); 26 | return nodeData && nodeData.assignedSlot || null; 27 | } 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /src/patches/Window.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | import * as utils from '../utils.js'; 11 | import {addEventListener, removeEventListener, dispatchEvent} from '../patch-events.js'; 12 | 13 | export const WindowPatches = utils.getOwnPropertyDescriptors({ 14 | 15 | // Ensure that `dispatchEvent` is patched directly on Window since on 16 | // IE11, Window does not descend from EventTarget. 17 | dispatchEvent, 18 | 19 | // NOTE: ensure these methods are bound to `window` so that `this` is correct 20 | // when called directly from global context without a receiver; e.g. 21 | // `addEventListener(...)`. 22 | addEventListener: addEventListener.bind(window), 23 | 24 | removeEventListener: removeEventListener.bind(window) 25 | 26 | }); -------------------------------------------------------------------------------- /src/shady-data.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | 12 | export class ShadyData { 13 | 14 | /** @override */ 15 | toJSON() { 16 | return {}; 17 | } 18 | } 19 | 20 | export function ensureShadyDataForNode(node) { 21 | if (!node.__shady) { 22 | node.__shady = new ShadyData(); 23 | } 24 | return node.__shady; 25 | } 26 | 27 | export function shadyDataForNode(node) { 28 | return node && node.__shady; 29 | } 30 | -------------------------------------------------------------------------------- /src/shadydom.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | /** 12 | * Patches elements that interacts with ShadyDOM 13 | * such that tree traversal and mutation apis act like they would under 14 | * ShadowDOM. 15 | * 16 | * This import enables seemless interaction with ShadyDOM powered 17 | * custom elements, enabling better interoperation with 3rd party code, 18 | * libraries, and frameworks that use DOM tree manipulation apis. 19 | */ 20 | 21 | import * as utils from './utils.js'; 22 | import {flush, enqueue} from './flush.js'; 23 | import {observeChildren, unobserveChildren, filterMutations} from './observe-changes.js'; 24 | import {addNativePrefixedProperties, nativeMethods, nativeTree} from './patch-native.js'; 25 | import {patchInsideElementAccessors, patchOutsideElementAccessors} from './patch-instances.js'; 26 | import {patchEvents, patchClick, composedPath} from './patch-events.js'; 27 | import {ShadyRoot} from './attach-shadow.js'; 28 | import {wrap, Wrapper} from './wrapper.js'; 29 | import {addShadyPrefixedProperties, applyPatches} from './patch-prototypes.js'; 30 | 31 | 32 | if (utils.settings.inUse) { 33 | 34 | let ShadyDOM = { 35 | // TODO(sorvell): remove when Polymer does not depend on this. 36 | 'inUse': utils.settings.inUse, 37 | // NOTE: old browsers without prototype accessors (very old Chrome 38 | // and Safari) need manually patched accessors to properly set 39 | // `innerHTML` and `textContent` when an element is: 40 | // (1) inside a shadowRoot 41 | // (2) does not have special (slot) children itself 42 | // (3) and setting the property needs to provoke distribution (because 43 | // a nested slot is added/removed) 44 | 'patch': (node) => { 45 | patchInsideElementAccessors(node); 46 | patchOutsideElementAccessors(node); 47 | return node; 48 | }, 49 | 'isShadyRoot': utils.isShadyRoot, 50 | 'enqueue': enqueue, 51 | 'flush': flush, 52 | 'flushInitial': (root) => { 53 | root._flushInitial(); 54 | }, 55 | 'settings': utils.settings, 56 | 'filterMutations': filterMutations, 57 | 'observeChildren': observeChildren, 58 | 'unobserveChildren': unobserveChildren, 59 | // Set to true to defer native custom elements connection until the 60 | // document has fully parsed. This enables custom elements that create 61 | // shadowRoots to be defined while the document is loading. Elements 62 | // customized as they are created by the parser will successfully 63 | // render with this flag on. 64 | 'deferConnectionCallbacks': utils.settings['deferConnectionCallbacks'], 65 | // Set to true to speed up the polyfill slightly at the cost of correctness 66 | // * does not patch querySelector/All on Document or DocumentFragment 67 | // * does not wrap connected/disconnected callbacks to de-dup these 68 | // when using native customElements 69 | // * does not wait to process children of elements with shadowRoots 70 | // meaning shadowRoots should not be created while an element is parsing 71 | // (e.g. if a custom element that creates a shadowRoot is defined before 72 | // a candidate element in the document below it. 73 | 'preferPerformance': utils.settings['preferPerformance'], 74 | // Integration point with ShadyCSS to disable styling MutationObserver, 75 | // as ShadyDOM will now handle dynamic scoping. 76 | 'handlesDynamicScoping': true, 77 | 'wrap': utils.settings.noPatch ? wrap : (n) => n, 78 | 'Wrapper': Wrapper, 79 | 'composedPath': composedPath, 80 | // Set to true to avoid patching regular platform property names. When set, 81 | // Shadow DOM compatible behavior is only available when accessing DOM 82 | // API using `ShadyDOM.wrap`, e.g. `ShadyDOM.wrap(element).shadowRoot`. 83 | // This setting provides a small performance boost, but requires all DOM API 84 | // access that requires Shadow DOM behavior to be proxied via `ShadyDOM.wrap`. 85 | 'noPatch': utils.settings.noPatch, 86 | 'nativeMethods': nativeMethods, 87 | 'nativeTree': nativeTree 88 | }; 89 | 90 | window['ShadyDOM'] = ShadyDOM; 91 | 92 | // Modifies native prototypes for Node, Element, etc. to 93 | // make native platform behavior available at prefixed names, e.g. 94 | // `utils.NATIVE_PREFIX + 'firstChild'` or `__shady_native_firstChild`. 95 | // This allows the standard names to be safely patched while retaining the 96 | // ability for native behavior to be used. This polyfill manipulates DOM 97 | // by using this saved native behavior. 98 | // Note, some browsers do not have proper element descriptors for 99 | // accessors; in this case, native behavior for these accessors is simulated 100 | // via a TreeWalker. 101 | addNativePrefixedProperties(); 102 | 103 | // Modifies native prototypes for Node, Element, etc. to make ShadowDOM 104 | // behavior available at prefixed names, e.g. 105 | // `utils.SHADY_PREFIX + 'firstChild` or `__shady_firstChild`. This is done 106 | // so this polyfill can perform Shadow DOM style DOM manipulation. 107 | // Because patching normal platform property names is optional, these prefixed 108 | // names are used internally. 109 | addShadyPrefixedProperties(); 110 | 111 | // Modifies native prototypes for Node, Element, etc. to patch 112 | // regular platform property names to have Shadow DOM compatible API behavior. 113 | // This applies the utils.SHADY_PREFIX behavior to normal names. For example, 114 | // if `noPatch` is not set, then `el.__shady_firstChild` is equivalent to 115 | // `el.firstChild`. 116 | // NOTE, on older browsers (old Chrome/Safari) native accessors cannot be 117 | // patched on prototypes (e.g. Node.prototype.firstChild cannot be modified). 118 | // On these browsers, instance level patching is performed where needed; this 119 | // instance patching is only done when `noPatch` is *not* set. 120 | if (!utils.settings.noPatch) { 121 | applyPatches(); 122 | // Patch click event behavior only if we're patching 123 | patchClick() 124 | } 125 | 126 | // For simplicity, patch events unconditionally. 127 | // Patches the event system to have Shadow DOM compatible behavior (e.g. 128 | // event retargeting). When `noPatch` is set, retargeting is only available 129 | // when adding event listeners and dispatching events via `ShadyDOM.wrap` 130 | // (e.g. `ShadyDOM.wrap(element).addEventListener(...)`). 131 | patchEvents(); 132 | 133 | window.ShadowRoot = /** @type {function(new:ShadowRoot)} */(ShadyRoot); 134 | } 135 | -------------------------------------------------------------------------------- /src/style-scoping.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from './utils.js'; 12 | 13 | let scopingShim = null; 14 | 15 | export function getScopingShim() { 16 | if (!scopingShim) { 17 | scopingShim = window['ShadyCSS'] && window['ShadyCSS']['ScopingShim']; 18 | } 19 | return scopingShim || null; 20 | } 21 | 22 | /** 23 | * @param {!Node} node 24 | * @param {string} attr 25 | * @param {string} value 26 | */ 27 | export function scopeClassAttribute(node, attr, value) { 28 | const scopingShim = getScopingShim(); 29 | if (scopingShim && attr === 'class') { 30 | scopingShim['setElementClass'](node, value); 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | /** 37 | * @param {!Node} node 38 | * @param {string} newScopeName 39 | */ 40 | export function addShadyScoping(node, newScopeName) { 41 | const scopingShim = getScopingShim(); 42 | if (!scopingShim) { 43 | return; 44 | } 45 | scopingShim['scopeNode'](node, newScopeName); 46 | } 47 | 48 | /** 49 | * @param {!Node} node 50 | * @param {string} currentScopeName 51 | */ 52 | export function removeShadyScoping(node, currentScopeName) { 53 | const scopingShim = getScopingShim(); 54 | if (!scopingShim) { 55 | return; 56 | } 57 | scopingShim['unscopeNode'](node, currentScopeName); 58 | } 59 | 60 | /** 61 | * @param {!Node} node 62 | * @param {string} newScopeName 63 | * @param {string} oldScopeName 64 | */ 65 | export function replaceShadyScoping(node, newScopeName, oldScopeName) { 66 | const scopingShim = getScopingShim(); 67 | if (!scopingShim) { 68 | return; 69 | } 70 | if (oldScopeName) { 71 | removeShadyScoping(node, oldScopeName); 72 | } 73 | addShadyScoping(node, newScopeName); 74 | } 75 | 76 | /** 77 | * @param {!Node} node 78 | * @param {string} newScopeName 79 | * @return {boolean} 80 | */ 81 | export function currentScopeIsCorrect(node, newScopeName) { 82 | const scopingShim = getScopingShim(); 83 | if (!scopingShim) { 84 | return true; 85 | } 86 | if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { 87 | // NOTE: as an optimization, only check that all the top-level children 88 | // have the correct scope. 89 | let correctScope = true; 90 | for (let n=node[utils.SHADY_PREFIX + 'firstChild']; n; n = n[utils.SHADY_PREFIX + 'nextSibling']) { 91 | correctScope = correctScope && 92 | currentScopeIsCorrect(n, newScopeName); 93 | } 94 | return correctScope; 95 | } 96 | if (node.nodeType !== Node.ELEMENT_NODE) { 97 | return true; 98 | } 99 | const currentScope = scopingShim['currentScopeForNode'](node); 100 | return currentScope === newScopeName; 101 | } 102 | 103 | /** 104 | * @param {!Node} node 105 | * @return {string} 106 | */ 107 | export function currentScopeForNode(node) { 108 | if (node.nodeType !== Node.ELEMENT_NODE) { 109 | return ''; 110 | } 111 | const scopingShim = getScopingShim(); 112 | if (!scopingShim) { 113 | return ''; 114 | } 115 | return scopingShim['currentScopeForNode'](node); 116 | } 117 | 118 | /** 119 | * Walk over a node's tree and apply visitorFn to each element node 120 | * 121 | * @param {Node} node 122 | * @param {function(!Node):void} visitorFn 123 | */ 124 | export function treeVisitor(node, visitorFn) { 125 | if (!node) { 126 | return; 127 | } 128 | // this check is necessary if `node` is a Document Fragment 129 | if (node.nodeType === Node.ELEMENT_NODE) { 130 | visitorFn(node); 131 | } 132 | for (let n = node[utils.SHADY_PREFIX + 'firstChild']; n; (n = n[utils.SHADY_PREFIX + 'nextSibling'])) { 133 | if (n.nodeType === Node.ELEMENT_NODE) { 134 | treeVisitor(n, visitorFn); 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | import {shadyDataForNode} from './shady-data.js'; 11 | 12 | /** @type {!Object} */ 13 | export const settings = window['ShadyDOM'] || {}; 14 | 15 | settings.hasNativeShadowDOM = Boolean(Element.prototype.attachShadow && Node.prototype.getRootNode); 16 | 17 | const desc = Object.getOwnPropertyDescriptor(Node.prototype, 'firstChild'); 18 | 19 | settings.hasDescriptors = Boolean(desc && desc.configurable && desc.get); 20 | settings.inUse = settings['force'] || !settings.hasNativeShadowDOM; 21 | settings.noPatch = settings['noPatch'] || false; 22 | settings.preferPerformance = settings['preferPerformance']; 23 | 24 | const IS_IE = navigator.userAgent.match('Trident'); 25 | settings.IS_IE = IS_IE; 26 | 27 | export const canUpgrade = () => !settings.IS_IE; 28 | 29 | export const isTrackingLogicalChildNodes = (node) => { 30 | const nodeData = shadyDataForNode(node); 31 | return (nodeData && nodeData.firstChild !== undefined); 32 | } 33 | 34 | export const isShadyRoot = (obj) => { 35 | return Boolean(obj._localName === 'ShadyRoot'); 36 | } 37 | 38 | export const hasShadowRootWithSlot = (node) => { 39 | const nodeData = shadyDataForNode(node); 40 | let root = nodeData && nodeData.root; 41 | return (root && root._hasInsertionPoint()); 42 | } 43 | 44 | let p = Element.prototype; 45 | let matches = p.matches || p.matchesSelector || 46 | p.mozMatchesSelector || p.msMatchesSelector || 47 | p.oMatchesSelector || p.webkitMatchesSelector; 48 | 49 | export const matchesSelector = (element, selector) => { 50 | return matches.call(element, selector); 51 | } 52 | 53 | export const mixin = (target, source) => { 54 | for (var i in source) { 55 | target[i] = source[i]; 56 | } 57 | return target; 58 | } 59 | 60 | // NOTE, prefer MutationObserver over Promise for microtask timing 61 | // for consistency x-platform. 62 | let twiddle = document.createTextNode(''); 63 | let content = 0; 64 | let queue = []; 65 | new MutationObserver(() => { 66 | while (queue.length) { 67 | // catch errors in user code... 68 | try { 69 | queue.shift()(); 70 | } catch(e) { 71 | // enqueue another record and throw 72 | twiddle.textContent = content++; 73 | throw(e); 74 | } 75 | } 76 | }).observe(twiddle, {characterData: true}); 77 | 78 | // use MutationObserver to get microtask async timing. 79 | export const microtask = (callback) => { 80 | queue.push(callback); 81 | twiddle.textContent = content++; 82 | } 83 | 84 | export const hasDocumentContains = Boolean(document.contains); 85 | 86 | export const contains = (container, node) => { 87 | while (node) { 88 | if (node == container) { 89 | return true; 90 | } 91 | node = node[SHADY_PREFIX + 'parentNode']; 92 | } 93 | return false; 94 | } 95 | 96 | const getNodeHTMLCollectionName = (node) => 97 | node.getAttribute('id') || node.getAttribute('name'); 98 | 99 | const isValidHTMLCollectionName = (name) => name !== 'length' && isNaN(name); 100 | 101 | export const createPolyfilledHTMLCollection = (nodes) => { 102 | // Note: loop in reverse so that the first named item matches the named property 103 | for (let l = nodes.length - 1; l >= 0; l--) { 104 | const node = nodes[l]; 105 | const name = getNodeHTMLCollectionName(node); 106 | 107 | if (name && isValidHTMLCollectionName(name)) { 108 | nodes[name] = node; 109 | } 110 | } 111 | nodes.item = function(index) { 112 | return nodes[index]; 113 | } 114 | nodes.namedItem = function(name) { 115 | if (isValidHTMLCollectionName(name) && nodes[name]) { 116 | return nodes[name]; 117 | } 118 | 119 | for (const node of nodes) { 120 | const nodeName = getNodeHTMLCollectionName(node); 121 | 122 | if (nodeName == name) { 123 | return node; 124 | } 125 | } 126 | 127 | return null; 128 | }; 129 | return nodes; 130 | } 131 | 132 | export const NATIVE_PREFIX = '__shady_native_'; 133 | export const SHADY_PREFIX = '__shady_'; 134 | 135 | export const nativeChildNodesArray = (parent) => { 136 | const result = []; 137 | for (let n=parent[NATIVE_PREFIX + 'firstChild']; n; n = n[NATIVE_PREFIX + 'nextSibling']) { 138 | result.push(n); 139 | } 140 | return result; 141 | } 142 | 143 | export const childNodesArray = (parent) => { 144 | const result = []; 145 | for (let n=parent[SHADY_PREFIX + 'firstChild']; n; n = n[SHADY_PREFIX + 'nextSibling']) { 146 | result.push(n); 147 | } 148 | return result; 149 | } 150 | 151 | /** 152 | * Patch a group of accessors on an object only if it exists or if the `force` 153 | * argument is true. 154 | * @param {!Object} proto 155 | * @param {!Object} descriptors 156 | * @param {string=} prefix 157 | * @param {Array=} disallowedPatches 158 | */ 159 | export const patchProperties = (proto, descriptors, prefix = '', disallowedPatches) => { 160 | for (let p in descriptors) { 161 | const newDescriptor = descriptors[p]; 162 | if (disallowedPatches && disallowedPatches.indexOf(p) >= 0) { 163 | continue; 164 | } 165 | newDescriptor.configurable = true; 166 | const name = prefix + p; 167 | // NOTE: we prefer writing directly because some browsers 168 | // have descriptors that are writable but not configurable (e.g. 169 | // `appendChild` on older browsers) 170 | if (newDescriptor.value) { 171 | proto[name] = newDescriptor.value; 172 | } else { 173 | // NOTE: this can throw if 'force' is used so catch the error. 174 | try { 175 | Object.defineProperty(proto, name, newDescriptor); 176 | } catch(e) { 177 | // this error is harmless so we just trap it. 178 | } 179 | } 180 | } 181 | } 182 | 183 | /** @type {!function(new:HTMLElement)} */ 184 | export const NativeHTMLElement = 185 | (window['customElements'] && window['customElements']['nativeHTMLElement']) || 186 | HTMLElement; 187 | 188 | // note, this is not a perfect polyfill since it doesn't include symbols 189 | /** @return {!Object} */ 190 | export const getOwnPropertyDescriptors = (obj) => { 191 | const descriptors = {}; 192 | Object.getOwnPropertyNames(obj).forEach((name) => { 193 | descriptors[name] = Object.getOwnPropertyDescriptor(obj, name); 194 | }); 195 | return descriptors; 196 | }; 197 | -------------------------------------------------------------------------------- /src/wrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license 3 | Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 4 | This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | Code distributed by Google as part of the polymer project is also 8 | subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | import * as utils from './utils.js'; 12 | import {eventPropertyNames} from './patch-events.js'; 13 | 14 | /** @implements {IWrapper} */ 15 | class Wrapper { 16 | 17 | /** @param {!Node} node */ 18 | constructor(node) { 19 | this.node = node; 20 | } 21 | 22 | // node 23 | addEventListener(name, fn, options) { 24 | return this.node[utils.SHADY_PREFIX + 'addEventListener'](name, fn, options); 25 | } 26 | 27 | removeEventListener(name, fn, options) { 28 | return this.node[utils.SHADY_PREFIX + 'removeEventListener'](name, fn, options); 29 | } 30 | 31 | appendChild(node) { 32 | return this.node[utils.SHADY_PREFIX + 'appendChild'](node); 33 | } 34 | 35 | insertBefore(node, ref_node) { 36 | return this.node[utils.SHADY_PREFIX + 'insertBefore'](node, ref_node); 37 | } 38 | 39 | removeChild(node) { 40 | return this.node[utils.SHADY_PREFIX + 'removeChild'](node); 41 | } 42 | 43 | replaceChild(node, ref_node) { 44 | return this.node[utils.SHADY_PREFIX + 'replaceChild'](node, ref_node); 45 | } 46 | 47 | cloneNode(deep) { 48 | return this.node[utils.SHADY_PREFIX + 'cloneNode'](deep); 49 | } 50 | 51 | getRootNode(options) { 52 | return this.node[utils.SHADY_PREFIX + 'getRootNode'](options); 53 | } 54 | 55 | contains(node) { 56 | return this.node[utils.SHADY_PREFIX + 'contains'](node); 57 | } 58 | 59 | dispatchEvent(event) { 60 | return this.node[utils.SHADY_PREFIX + 'dispatchEvent'](event); 61 | } 62 | 63 | // element 64 | setAttribute(name, value) { 65 | this.node[utils.SHADY_PREFIX + 'setAttribute'](name, value); 66 | } 67 | 68 | // NOTE: not needed, just here for balance 69 | getAttribute(name) { 70 | return this.node[utils.NATIVE_PREFIX + 'getAttribute'](name); 71 | } 72 | 73 | // NOTE: not needed, just here for balance 74 | hasAttribute(name) { 75 | return this.node[utils.NATIVE_PREFIX + 'hasAttribute'](name); 76 | } 77 | 78 | removeAttribute(name) { 79 | this.node[utils.SHADY_PREFIX + 'removeAttribute'](name); 80 | } 81 | 82 | attachShadow(options) { 83 | return this.node[utils.SHADY_PREFIX + 'attachShadow'](options); 84 | } 85 | 86 | /** @return {!Node|undefined} */ 87 | get activeElement() { 88 | if (utils.isShadyRoot(this.node) || this.node.nodeType === Node.DOCUMENT_NODE) { 89 | const e = this.node[utils.SHADY_PREFIX + 'activeElement']; 90 | return e; 91 | } 92 | } 93 | 94 | /** 95 | * Installed for compatibility with browsers (older Chrome/Safari) that do 96 | * not have a configurable `activeElement` accessor. Enables noPatch and 97 | * patch mode both to consistently use ShadyDOM.wrap(document)._activeElement. 98 | * @override 99 | * @return {!Node|undefined} 100 | */ 101 | get _activeElement() { 102 | return this.activeElement; 103 | } 104 | 105 | // NOTE: not needed, just here for balance 106 | /** @override */ 107 | focus() { 108 | this.node[utils.NATIVE_PREFIX + 'focus'](); 109 | } 110 | 111 | blur() { 112 | this.node[utils.SHADY_PREFIX + 'blur'](); 113 | } 114 | 115 | // document 116 | importNode(node, deep) { 117 | if (this.node.nodeType === Node.DOCUMENT_NODE) { 118 | return this.node[utils.SHADY_PREFIX + 'importNode'](node, deep); 119 | } 120 | } 121 | 122 | getElementById(id) { 123 | if (this.node.nodeType === Node.DOCUMENT_NODE) { 124 | return this.node[utils.SHADY_PREFIX + 'getElementById'](id); 125 | } 126 | } 127 | 128 | // query 129 | querySelector(selector) { 130 | return this.node[utils.SHADY_PREFIX + 'querySelector'](selector); 131 | } 132 | 133 | querySelectorAll(selector, useNative) { 134 | return this.node[utils.SHADY_PREFIX + 'querySelectorAll'](selector, useNative); 135 | } 136 | 137 | // slot 138 | assignedNodes(options) { 139 | if (this.node.localName === 'slot') { 140 | return this.node[utils.SHADY_PREFIX + 'assignedNodes'](options); 141 | } 142 | } 143 | 144 | get host() { 145 | if (utils.isShadyRoot(this.node)) { 146 | return /** @type {!ShadowRoot} */(this.node).host; 147 | } 148 | } 149 | 150 | get parentNode() { 151 | return this.node[utils.SHADY_PREFIX + 'parentNode']; 152 | } 153 | 154 | get firstChild() { 155 | return this.node[utils.SHADY_PREFIX + 'firstChild']; 156 | } 157 | 158 | get lastChild() { 159 | return this.node[utils.SHADY_PREFIX + 'lastChild']; 160 | } 161 | 162 | get nextSibling() { 163 | return this.node[utils.SHADY_PREFIX + 'nextSibling']; 164 | } 165 | 166 | get previousSibling() { 167 | return this.node[utils.SHADY_PREFIX + 'previousSibling']; 168 | } 169 | 170 | get childNodes() { 171 | return this.node[utils.SHADY_PREFIX + 'childNodes']; 172 | } 173 | 174 | get parentElement() { 175 | return this.node[utils.SHADY_PREFIX + 'parentElement']; 176 | } 177 | 178 | get firstElementChild() { 179 | return this.node[utils.SHADY_PREFIX + 'firstElementChild']; 180 | } 181 | 182 | get lastElementChild() { 183 | return this.node[utils.SHADY_PREFIX + 'lastElementChild']; 184 | } 185 | 186 | get nextElementSibling() { 187 | return this.node[utils.SHADY_PREFIX + 'nextElementSibling']; 188 | } 189 | 190 | get previousElementSibling() { 191 | return this.node[utils.SHADY_PREFIX + 'previousElementSibling']; 192 | } 193 | 194 | get children() { 195 | return this.node[utils.SHADY_PREFIX + 'children']; 196 | } 197 | 198 | get childElementCount() { 199 | return this.node[utils.SHADY_PREFIX + 'childElementCount']; 200 | } 201 | 202 | get shadowRoot() { 203 | return this.node[utils.SHADY_PREFIX + 'shadowRoot']; 204 | } 205 | 206 | get assignedSlot() { 207 | return this.node[utils.SHADY_PREFIX + 'assignedSlot']; 208 | } 209 | 210 | get isConnected() { 211 | return this.node[utils.SHADY_PREFIX + 'isConnected']; 212 | } 213 | 214 | get innerHTML() { 215 | return this.node[utils.SHADY_PREFIX + 'innerHTML']; 216 | } 217 | 218 | set innerHTML(value) { 219 | this.node[utils.SHADY_PREFIX + 'innerHTML'] = value; 220 | } 221 | 222 | get textContent() { 223 | return this.node[utils.SHADY_PREFIX + 'textContent']; 224 | } 225 | 226 | set textContent(value) { 227 | this.node[utils.SHADY_PREFIX + 'textContent'] = value; 228 | } 229 | 230 | get slot() { 231 | return this.node[utils.SHADY_PREFIX + 'slot']; 232 | } 233 | 234 | set slot(value) { 235 | this.node[utils.SHADY_PREFIX + 'slot'] = value; 236 | } 237 | 238 | get className() { 239 | return this.node[utils.SHADY_PREFIX + 'className']; 240 | } 241 | 242 | set className(value) { 243 | return this.node[utils.SHADY_PREFIX + 'className'] = value; 244 | } 245 | 246 | } 247 | 248 | eventPropertyNames.forEach(name => { 249 | Object.defineProperty(Wrapper.prototype, name, { 250 | /** @this {Wrapper} */ 251 | get() { 252 | return this.node[utils.SHADY_PREFIX + name]; 253 | }, 254 | /** @this {Wrapper} */ 255 | set(value) { 256 | this.node[utils.SHADY_PREFIX + name] = value; 257 | }, 258 | configurable: true 259 | }); 260 | 261 | }); 262 | 263 | export {Wrapper}; 264 | 265 | const wrapperMap = new WeakMap(); 266 | 267 | export function wrap(obj) { 268 | if (utils.isShadyRoot(obj) || obj instanceof Wrapper) { 269 | return obj; 270 | } 271 | let wrapper = wrapperMap.get(obj) 272 | if (!wrapper) { 273 | wrapper = new Wrapper(obj); 274 | wrapperMap.set(obj, wrapper); 275 | } 276 | return wrapper; 277 | } 278 | -------------------------------------------------------------------------------- /tests/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "ShadyDOM": true, 7 | "assert": false, 8 | "defineTestElement": true 9 | } 10 | } -------------------------------------------------------------------------------- /tests/attach-while-loading.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 75 | 76 | 77 |
light
78 |
79 | 80 | 81 |
light
82 |
83 | 84 | 85 | 86 | 87 | 88 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /tests/connect-disconnect.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 92 | 93 | 94 | 97 | 98 | 101 | 102 | 105 | 106 | 110 | 111 | 114 | 115 | 128 | 129 | 132 | 133 | 146 | 147 | 148 | 149 | 150 | 151 | 347 | 348 | 349 | 350 | -------------------------------------------------------------------------------- /tests/filter-mutations.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /tests/loader.js: -------------------------------------------------------------------------------- 1 | ShadyDOM = { 2 | force: true, 3 | noPatch: !!window.location.search.match('noPatch'), 4 | preferPerformance: !!window.location.search.match('preferPerformance') 5 | }; 6 | 7 | // TODO(sorvell): noPatching does not work with the custom elements polyfill. 8 | // IF the polyfill used `ShadyDOM.wrap` throughout, it could be made to work. 9 | if (window.customElements) { 10 | customElements.forcePolyfill = window.location.search.match('forceCustomElements'); 11 | } 12 | 13 | const loadScript = (src) => { 14 | document.write(` 15 | 16 | 17 | 23 | 24 | 25 | 37 | 38 | 39 | 40 | 41 | 42 | 81 | 82 | 83 | 86 | 87 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /tests/observeChildren.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /tests/perf/composed-children-perf.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /tests/prefer-performance.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 27 | 28 | 29 | 41 | 42 | 43 | 44 | 45 | 46 |
Hi
47 | 48 | 88 | 89 | 92 | 95 | 96 | 97 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /tests/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | ShadyDOM Tests 12 | 13 | 14 | 15 | 16 | 17 | 71 | -------------------------------------------------------------------------------- /tests/slot-scenarios.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 42 | 43 | 48 | 49 | 52 | 53 | 58 | 59 | 62 | 63 | 215 | -------------------------------------------------------------------------------- /tests/slotchange.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /tests/smoke/click.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 34 | 49 |

addEventListener()

50 | 51 | 52 | 55 | 56 |

onclick=

57 | 58 | 59 |

60 |     
61 | 62 | 63 | -------------------------------------------------------------------------------- /tests/smoke/data-table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
NamePositionOfficeAgeStart dateSalary
NamePositionOfficeAgeStart dateSalary
Tiger NixonSystem ArchitectEdinburgh612011/04/25$320,800
Garrett WintersAccountantTokyo632011/07/25$170,750
63 | 64 | 65 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /tests/smoke/focus-api.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 |
22 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/smoke/focus.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 |
21 | 25 |
26 |
27 | 32 |
33 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/smoke/property-info.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /tests/smoke/rel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/smoke/smoke-nopatch.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 |
simple
22 | 23 |
24 |
25 | distribution content 26 |
27 | 28 |
29 | 30 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /tests/smoke/smoke.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 |
simple
22 | 23 |
24 |
25 | distribution content 26 |
27 | 28 |
29 | 30 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tests/sync-style-scoping.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 41 | 42 | 43 | 53 | 54 | 55 | 56 | 57 | 63 | 82 | 87 |
88 | 405 | 406 | -------------------------------------------------------------------------------- /tests/wct-browser-config.js: -------------------------------------------------------------------------------- 1 | window.WCT = { 2 | environmentScripts: [ 3 | 'stacky/lib/parsing.js', 4 | 'stacky/lib/formatting.js', 5 | 'stacky/lib/normalization.js', 6 | 'mocha/mocha.js', 7 | 'chai/chai.js', 8 | '@polymer/sinonjs/sinon.js', 9 | 'accessibility-developer-tools/dist/js/axs_testing.js', 10 | '@polymer/test-fixture/test-fixture.js' 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /wct.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "suites": ["tests/runner.html"], 3 | "npm": true, 4 | "plugins": { 5 | "local": { 6 | "browserOptions": { 7 | "chrome": [ 8 | "headless", 9 | "disable-gpu", 10 | "no-sandbox" 11 | ] 12 | } 13 | } 14 | } 15 | } 16 | --------------------------------------------------------------------------------