x/tools/cmd/heapview: add a heading to the page
This CL's purpose is to introduce the dependency on the HTML Custom Elements polyfill. Like we've done so far, I'm trying to keep dependencies light by using current or polyfilling future webcomponents standards. Change-Id: I11d14db367b697cdd527fb66b9d7d160ac244b78 Reviewed-on: https://go-review.googlesource.com/25494 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
7ef02fdb22
commit
337c0124d7
|
@ -2,6 +2,49 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hamburger menu element.
|
||||||
|
*/
|
||||||
|
class HamburgerElement extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
this.innerHTML = '☰'; // Unicode character for hamburger menu.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.customElements.define('heap-hamburger', HamburgerElement);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A heading for the page with a hamburger menu and a title.
|
||||||
|
*/
|
||||||
|
export class HeadingElement extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
this.style.display = 'block';
|
||||||
|
this.style.backgroundColor = '#2196F3';
|
||||||
|
this.style.webkitUserSelect = 'none';
|
||||||
|
this.style.cursor = 'default';
|
||||||
|
this.style.color = '#FFFFFF';
|
||||||
|
this.style.padding = '10px';
|
||||||
|
this.innerHTML = `
|
||||||
|
<div style="margin:0px; font-size:2em"><heap-hamburger></heap-hamburger> Go Heap Viewer</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.customElements.define('heap-heading', HeadingElement);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset body's margin and padding, and set font.
|
||||||
|
*/
|
||||||
|
function clearStyle() {
|
||||||
|
document.head.innerHTML += `
|
||||||
|
<style>
|
||||||
|
* {font-family: Roboto,Helvetica}
|
||||||
|
body {margin: 0px; padding:0px}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
document.title = 'Go Heap Viewer';
|
document.title = 'Go Heap Viewer';
|
||||||
|
clearStyle();
|
||||||
|
document.body.appendChild(new HeadingElement());
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,14 @@
|
||||||
|
|
||||||
import {main} from './main';
|
import {main} from './main';
|
||||||
|
|
||||||
describe("main", () => {
|
describe('main', () => {
|
||||||
it('sets the document\'s title', () => {
|
it('sets the document\'s title', () => {
|
||||||
main();
|
main();
|
||||||
expect(document.title).toBe('Go Heap Viewer');
|
expect(document.title).toBe('Go Heap Viewer');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('has a heading', () => {
|
||||||
|
main();
|
||||||
|
expect(document.querySelector('heap-heading')).toBeDefined();
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -29,6 +29,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "karma start testing/karma.conf.js",
|
"test": "karma start testing/karma.conf.js",
|
||||||
"format": "find . | grep '\\(test_main\\.js\\|\\.ts\\)$' | xargs clang-format -i"
|
"format": "find . | grep '\\(test_main\\.js\\|\\.ts\\)$' | xargs clang-format -i",
|
||||||
|
"lint": "tslint --project ."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ module.exports = config => {
|
||||||
frameworks: ['jasmine'],
|
frameworks: ['jasmine'],
|
||||||
basePath: '../../../..',
|
basePath: '../../../..',
|
||||||
files: [
|
files: [
|
||||||
|
'third_party/webcomponents/customelements.js',
|
||||||
'third_party/typescript/typescript.js',
|
'third_party/typescript/typescript.js',
|
||||||
'third_party/moduleloader/moduleloader.js',
|
'third_party/moduleloader/moduleloader.js',
|
||||||
'cmd/heapview/client/testing/test_main.js',
|
'cmd/heapview/client/testing/test_main.js',
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
// Configure module loader.
|
// Configure module loader.
|
||||||
System.transpiler = 'typescript'
|
System.transpiler = 'typescript'
|
||||||
|
System.typescriptOptions = {
|
||||||
|
target: ts.ScriptTarget.ES2015
|
||||||
|
};
|
||||||
System.locate = (load) => load.name + '.ts';
|
System.locate = (load) => load.name + '.ts';
|
||||||
|
|
||||||
// Determine set of test files.
|
// Determine set of test files.
|
||||||
|
|
|
@ -18,10 +18,12 @@ import (
|
||||||
var port = flag.Int("port", 8080, "service port")
|
var port = flag.Int("port", 8080, "service port")
|
||||||
|
|
||||||
var index = `<!DOCTYPE html>
|
var index = `<!DOCTYPE html>
|
||||||
|
<script src="js/customelements.js"></script>
|
||||||
<script src="js/typescript.js"></script>
|
<script src="js/typescript.js"></script>
|
||||||
<script src="js/moduleloader.js"></script>
|
<script src="js/moduleloader.js"></script>
|
||||||
<script>
|
<script>
|
||||||
System.transpiler = 'typescript';
|
System.transpiler = 'typescript';
|
||||||
|
System.typescriptOptions = {target: ts.ScriptTarget.ES2015};
|
||||||
System.locate = (load) => load.name + '.ts';
|
System.locate = (load) => load.name + '.ts';
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
@ -55,6 +57,9 @@ var addHandlers = func() {
|
||||||
http.HandleFunc("/js/moduleloader.js", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/js/moduleloader.js", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.ServeFile(w, r, filepath.Join(toolsDir(), "third_party/moduleloader/moduleloader.js"))
|
http.ServeFile(w, r, filepath.Join(toolsDir(), "third_party/moduleloader/moduleloader.js"))
|
||||||
})
|
})
|
||||||
|
http.HandleFunc("/js/customelements.js", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.ServeFile(w, r, filepath.Join(toolsDir(), "third_party/webcomponents/customelements.js"))
|
||||||
|
})
|
||||||
|
|
||||||
// Serve index.html using html string above.
|
// Serve index.html using html string above.
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2015 The Polymer Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,597 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||||
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
* Code distributed by Google as part of the polymer project is also
|
||||||
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2.3
|
||||||
|
* http://w3c.github.io/webcomponents/spec/custom/#dfn-element-definition
|
||||||
|
* @typedef {{
|
||||||
|
* name: string,
|
||||||
|
* localName: string,
|
||||||
|
* constructor: Function,
|
||||||
|
* connectedCallback: Function,
|
||||||
|
* disconnectedCallback: Function,
|
||||||
|
* attributeChangedCallback: Function,
|
||||||
|
* observedAttributes: Array<string>,
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
var CustomElementDefinition;
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var doc = document;
|
||||||
|
var win = window;
|
||||||
|
|
||||||
|
// name validation
|
||||||
|
// https://html.spec.whatwg.org/multipage/scripting.html#valid-custom-element-name
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @const
|
||||||
|
* @type {Array<string>}
|
||||||
|
*/
|
||||||
|
var reservedTagList = [
|
||||||
|
'annotation-xml',
|
||||||
|
'color-profile',
|
||||||
|
'font-face',
|
||||||
|
'font-face-src',
|
||||||
|
'font-face-uri',
|
||||||
|
'font-face-format',
|
||||||
|
'font-face-name',
|
||||||
|
'missing-glyph',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @const */
|
||||||
|
var customNameValidation = /^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/;
|
||||||
|
function isValidCustomElementName(name) {
|
||||||
|
return customNameValidation.test(name) && reservedTagList.indexOf(name) === -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTreeWalker(root) {
|
||||||
|
// IE 11 requires the third and fourth arguments be present. If the third
|
||||||
|
// arg is null, it applies the default behaviour. However IE also requires
|
||||||
|
// the fourth argument be present even though the other browsers ignore it.
|
||||||
|
return doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isElement(node) {
|
||||||
|
return node.nodeType === Node.ELEMENT_NODE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registry of custom element definitions.
|
||||||
|
*
|
||||||
|
* See https://html.spec.whatwg.org/multipage/scripting.html#customelementsregistry
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @property {boolean} polyfilled Whether this registry is polyfilled
|
||||||
|
* @property {boolean} enableFlush Set to true to enable the flush() method
|
||||||
|
* to work. This should only be done for tests, as it causes a memory leak.
|
||||||
|
*/
|
||||||
|
function CustomElementsRegistry() {
|
||||||
|
/** @private {Map<string, CustomElementDefinition>} **/
|
||||||
|
this._definitions = new Map();
|
||||||
|
|
||||||
|
/** @private {Map<Function, CustomElementDefinition>} **/
|
||||||
|
this._constructors = new Map();
|
||||||
|
|
||||||
|
this._whenDefinedMap = new Map();
|
||||||
|
|
||||||
|
/** @private {Set<MutationObserver>} **/
|
||||||
|
this._observers = new Set();
|
||||||
|
|
||||||
|
/** @private {MutationObserver} **/
|
||||||
|
this._attributeObserver =
|
||||||
|
new MutationObserver(this._handleAttributeChange.bind(this));
|
||||||
|
|
||||||
|
/** @private {HTMLElement} **/
|
||||||
|
this._newInstance = null;
|
||||||
|
|
||||||
|
this.polyfilled = true;
|
||||||
|
this.enableFlush = false;
|
||||||
|
|
||||||
|
this._observeRoot(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomElementsRegistry.prototype = {
|
||||||
|
|
||||||
|
// HTML spec part 4.13.4
|
||||||
|
// https://html.spec.whatwg.org/multipage/scripting.html#dom-customelementsregistry-define
|
||||||
|
define: function(name, constructor, options) {
|
||||||
|
name = name.toString().toLowerCase();
|
||||||
|
|
||||||
|
// 1:
|
||||||
|
if (typeof constructor !== 'function') {
|
||||||
|
throw new TypeError('constructor must be a Constructor');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. If constructor is an interface object whose corresponding interface
|
||||||
|
// either is HTMLElement or has HTMLElement in its set of inherited
|
||||||
|
// interfaces, throw a TypeError and abort these steps.
|
||||||
|
//
|
||||||
|
// It doesn't appear possible to check this condition from script
|
||||||
|
|
||||||
|
// 3:
|
||||||
|
if (!isValidCustomElementName(name)) {
|
||||||
|
throw new SyntaxError(`The element name '${name}' is not valid.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4, 5:
|
||||||
|
// Note: we don't track being-defined names and constructors because
|
||||||
|
// define() isn't normally reentrant. The only time user code can run
|
||||||
|
// during define() is when getting callbacks off the prototype, which
|
||||||
|
// would be highly-unusual. We can make define() reentrant-safe if needed.
|
||||||
|
if (this._definitions.has(name)) {
|
||||||
|
throw new Error(`An element with name '${name}' is already defined`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6, 7:
|
||||||
|
if (this._constructors.has(constructor)) {
|
||||||
|
throw new Error(`Definition failed for '${name}': ` +
|
||||||
|
`The constructor is already used.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8:
|
||||||
|
var localName = name;
|
||||||
|
|
||||||
|
// 9, 10: We do not support extends currently.
|
||||||
|
|
||||||
|
// 11, 12, 13: Our define() isn't rentrant-safe
|
||||||
|
|
||||||
|
// 14.1:
|
||||||
|
var prototype = constructor.prototype;
|
||||||
|
|
||||||
|
// 14.2:
|
||||||
|
if (typeof prototype !== 'object') {
|
||||||
|
throw new TypeError(`Definition failed for '${name}': ` +
|
||||||
|
`constructor.prototype must be an object`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCallback(calllbackName) {
|
||||||
|
var callback = prototype[calllbackName];
|
||||||
|
if (callback !== undefined && typeof callback !== 'function') {
|
||||||
|
throw new Error(`${localName} '${calllbackName}' is not a Function`);
|
||||||
|
}
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3, 4:
|
||||||
|
var connectedCallback = getCallback('connectedCallback');
|
||||||
|
|
||||||
|
// 5, 6:
|
||||||
|
var disconnectedCallback = getCallback('disconnectedCallback');
|
||||||
|
|
||||||
|
// Divergence from spec: we always throw if attributeChangedCallback is
|
||||||
|
// not a function, and always get observedAttributes.
|
||||||
|
|
||||||
|
// 7, 9.1:
|
||||||
|
var attributeChangedCallback = getCallback('attributeChangedCallback');
|
||||||
|
|
||||||
|
// 8, 9.2, 9.3:
|
||||||
|
var observedAttributes = constructor['observedAttributes'] || [];
|
||||||
|
|
||||||
|
// 15:
|
||||||
|
// @type {CustomElementDefinition}
|
||||||
|
var definition = {
|
||||||
|
name: name,
|
||||||
|
localName: localName,
|
||||||
|
constructor: constructor,
|
||||||
|
connectedCallback: connectedCallback,
|
||||||
|
disconnectedCallback: disconnectedCallback,
|
||||||
|
attributeChangedCallback: attributeChangedCallback,
|
||||||
|
observedAttributes: observedAttributes,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 16:
|
||||||
|
this._definitions.set(localName, definition);
|
||||||
|
this._constructors.set(constructor, localName);
|
||||||
|
|
||||||
|
// 17, 18, 19:
|
||||||
|
this._addNodes(doc.childNodes);
|
||||||
|
|
||||||
|
// 20:
|
||||||
|
var deferred = this._whenDefinedMap.get(localName);
|
||||||
|
if (deferred) {
|
||||||
|
deferred.resolve(undefined);
|
||||||
|
this._whenDefinedMap.delete(localName);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the constructor defined for `name`, or `null`.
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @return {Function|undefined}
|
||||||
|
*/
|
||||||
|
get: function(name) {
|
||||||
|
// https://html.spec.whatwg.org/multipage/scripting.html#custom-elements-api
|
||||||
|
var def = this._definitions.get(name);
|
||||||
|
return def ? def.constructor : undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a `Promise` that resolves when a custom element for `name` has
|
||||||
|
* been defined.
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
whenDefined: function(name) {
|
||||||
|
// https://html.spec.whatwg.org/multipage/scripting.html#dom-customelementsregistry-whendefined
|
||||||
|
if (!customNameValidation.test(name)) {
|
||||||
|
return Promise.reject(
|
||||||
|
new SyntaxError(`The element name '${name}' is not valid.`));
|
||||||
|
}
|
||||||
|
if (this._definitions.has(name)) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
var deferred = {
|
||||||
|
promise: null,
|
||||||
|
};
|
||||||
|
deferred.promise = new Promise(function(resolve, _) {
|
||||||
|
deferred.resolve = resolve;
|
||||||
|
});
|
||||||
|
this._whenDefinedMap.set(name, deferred);
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Causes all pending mutation records to be processed, and thus all
|
||||||
|
* customization, upgrades and custom element reactions to be called.
|
||||||
|
* `enableFlush` must be true for this to work. Only use during tests!
|
||||||
|
*/
|
||||||
|
flush: function() {
|
||||||
|
if (this.enableFlush) {
|
||||||
|
console.warn("flush!!!");
|
||||||
|
this._observers.forEach(function(observer) {
|
||||||
|
this._handleMutations(observer.takeRecords());
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_setNewInstance: function(instance) {
|
||||||
|
this._newInstance = instance;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes a DOM root for mutations that trigger upgrades and reactions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_observeRoot: function(root) {
|
||||||
|
root.__observer = new MutationObserver(this._handleMutations.bind(this));
|
||||||
|
root.__observer.observe(root, {childList: true, subtree: true});
|
||||||
|
if (this.enableFlush) {
|
||||||
|
// this is memory leak, only use in tests
|
||||||
|
this._observers.add(root.__observer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_unobserveRoot: function(root) {
|
||||||
|
if (root.__observer) {
|
||||||
|
root.__observer.disconnect();
|
||||||
|
root.__observer = null;
|
||||||
|
if (this.enableFlush) {
|
||||||
|
this._observers.delete(root.__observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleMutations: function(mutations) {
|
||||||
|
for (var i = 0; i < mutations.length; i++) {
|
||||||
|
var mutation = mutations[i];
|
||||||
|
if (mutation.type === 'childList') {
|
||||||
|
// Note: we can't get an ordering between additions and removals, and
|
||||||
|
// so might diverge from spec reaction ordering
|
||||||
|
this._addNodes(mutation.addedNodes);
|
||||||
|
this._removeNodes(mutation.removedNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {NodeList} nodeList
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_addNodes: function(nodeList) {
|
||||||
|
for (var i = 0; i < nodeList.length; i++) {
|
||||||
|
var root = nodeList[i];
|
||||||
|
|
||||||
|
if (!isElement(root)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we're adding this node to an observed tree, we can unobserve
|
||||||
|
this._unobserveRoot(root);
|
||||||
|
|
||||||
|
var walker = createTreeWalker(root);
|
||||||
|
do {
|
||||||
|
var node = /** @type {HTMLElement} */ (walker.currentNode);
|
||||||
|
var definition = this._definitions.get(node.localName);
|
||||||
|
if (definition) {
|
||||||
|
if (!node.__upgraded) {
|
||||||
|
this._upgradeElement(node, definition, true);
|
||||||
|
}
|
||||||
|
if (node.__upgraded && !node.__attached) {
|
||||||
|
node.__attached = true;
|
||||||
|
if (definition && definition.connectedCallback) {
|
||||||
|
definition.connectedCallback.call(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.shadowRoot) {
|
||||||
|
// TODO(justinfagnani): do we need to check that the shadowRoot
|
||||||
|
// is observed?
|
||||||
|
this._addNodes(node.shadowRoot.childNodes);
|
||||||
|
}
|
||||||
|
if (node.tagName === 'LINK') {
|
||||||
|
var onLoad = (function() {
|
||||||
|
var link = node;
|
||||||
|
return function() {
|
||||||
|
link.removeEventListener('load', onLoad);
|
||||||
|
this._observeRoot(link.import);
|
||||||
|
this._addNodes(link.import.childNodes);
|
||||||
|
}.bind(this);
|
||||||
|
}).bind(this)();
|
||||||
|
if (node.import) {
|
||||||
|
onLoad();
|
||||||
|
} else {
|
||||||
|
node.addEventListener('load', onLoad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (walker.nextNode())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {NodeList} nodeList
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_removeNodes: function(nodeList) {
|
||||||
|
for (var i = 0; i < nodeList.length; i++) {
|
||||||
|
var root = nodeList[i];
|
||||||
|
|
||||||
|
if (!isElement(root)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we're detatching this element from an observed root, we need to
|
||||||
|
// reobserve it.
|
||||||
|
// TODO(justinfagnani): can we do this in a microtask so we don't thrash
|
||||||
|
// on creating and destroying MutationObservers on batch DOM mutations?
|
||||||
|
this._observeRoot(root);
|
||||||
|
|
||||||
|
var walker = createTreeWalker(root);
|
||||||
|
do {
|
||||||
|
var node = walker.currentNode;
|
||||||
|
if (node.__upgraded && node.__attached) {
|
||||||
|
node.__attached = false;
|
||||||
|
var definition = this._definitions.get(node.localName);
|
||||||
|
if (definition && definition.disconnectedCallback) {
|
||||||
|
definition.disconnectedCallback.call(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (walker.nextNode())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrades or customizes a custom element.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} element
|
||||||
|
* @param {CustomElementDefinition} definition
|
||||||
|
* @param {boolean} callConstructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_upgradeElement: function(element, definition, callConstructor) {
|
||||||
|
var prototype = definition.constructor.prototype;
|
||||||
|
element.__proto__ = prototype;
|
||||||
|
if (callConstructor) {
|
||||||
|
this._setNewInstance(element);
|
||||||
|
element.__upgraded = true;
|
||||||
|
new (definition.constructor)();
|
||||||
|
console.assert(this._newInstance == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var observedAttributes = definition.observedAttributes;
|
||||||
|
if (definition.attributeChangedCallback && observedAttributes.length > 0) {
|
||||||
|
this._attributeObserver.observe(element, {
|
||||||
|
attributes: true,
|
||||||
|
attributeOldValue: true,
|
||||||
|
attributeFilter: observedAttributes,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger attributeChangedCallback for existing attributes.
|
||||||
|
// https://html.spec.whatwg.org/multipage/scripting.html#upgrades
|
||||||
|
for (var i = 0; i < observedAttributes.length; i++) {
|
||||||
|
var name = observedAttributes[i];
|
||||||
|
if (element.hasAttribute(name)) {
|
||||||
|
var value = element.getAttribute(name);
|
||||||
|
element.attributeChangedCallback(name, null, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleAttributeChange: function(mutations) {
|
||||||
|
for (var i = 0; i < mutations.length; i++) {
|
||||||
|
var mutation = mutations[i];
|
||||||
|
if (mutation.type === 'attributes') {
|
||||||
|
var name = mutation.attributeName;
|
||||||
|
var oldValue = mutation.oldValue;
|
||||||
|
var target = mutation.target;
|
||||||
|
var newValue = target.getAttribute(name);
|
||||||
|
var namespace = mutation.attributeNamespace;
|
||||||
|
target['attributeChangedCallback'](name, oldValue, newValue, namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closure Compiler Exports
|
||||||
|
window['CustomElementsRegistry'] = CustomElementsRegistry;
|
||||||
|
CustomElementsRegistry.prototype['define'] = CustomElementsRegistry.prototype.define;
|
||||||
|
CustomElementsRegistry.prototype['get'] = CustomElementsRegistry.prototype.get;
|
||||||
|
CustomElementsRegistry.prototype['whenDefined'] = CustomElementsRegistry.prototype.whenDefined;
|
||||||
|
CustomElementsRegistry.prototype['flush'] = CustomElementsRegistry.prototype.flush;
|
||||||
|
CustomElementsRegistry.prototype['polyfilled'] = CustomElementsRegistry.prototype.polyfilled;
|
||||||
|
CustomElementsRegistry.prototype['enableFlush'] = CustomElementsRegistry.prototype.enableFlush;
|
||||||
|
|
||||||
|
// patch window.HTMLElement
|
||||||
|
|
||||||
|
var origHTMLElement = win.HTMLElement;
|
||||||
|
win.HTMLElement = function HTMLElement() {
|
||||||
|
var customElements = win['customElements'];
|
||||||
|
if (customElements._newInstance) {
|
||||||
|
var i = customElements._newInstance;
|
||||||
|
customElements._newInstance = null;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
if (this.constructor) {
|
||||||
|
var tagName = customElements._constructors.get(this.constructor);
|
||||||
|
return doc._createElement(tagName, false);
|
||||||
|
}
|
||||||
|
throw new Error('unknown constructor. Did you call customElements.define()?');
|
||||||
|
}
|
||||||
|
win.HTMLElement.prototype = Object.create(origHTMLElement.prototype);
|
||||||
|
Object.defineProperty(win.HTMLElement.prototype, 'constructor', {value: win.HTMLElement});
|
||||||
|
|
||||||
|
// patch all built-in subclasses of HTMLElement to inherit from the new HTMLElement
|
||||||
|
// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
|
||||||
|
|
||||||
|
/** @const */
|
||||||
|
var htmlElementSubclasses = [
|
||||||
|
'Button',
|
||||||
|
'Canvas',
|
||||||
|
'Data',
|
||||||
|
'Head',
|
||||||
|
'Mod',
|
||||||
|
'TableCell',
|
||||||
|
'TableCol',
|
||||||
|
'Anchor',
|
||||||
|
'Area',
|
||||||
|
'Base',
|
||||||
|
'Body',
|
||||||
|
'BR',
|
||||||
|
'DataList',
|
||||||
|
'Details',
|
||||||
|
'Dialog',
|
||||||
|
'Div',
|
||||||
|
'DList',
|
||||||
|
'Embed',
|
||||||
|
'FieldSet',
|
||||||
|
'Form',
|
||||||
|
'Heading',
|
||||||
|
'HR',
|
||||||
|
'Html',
|
||||||
|
'IFrame',
|
||||||
|
'Image',
|
||||||
|
'Input',
|
||||||
|
'Keygen',
|
||||||
|
'Label',
|
||||||
|
'Legend',
|
||||||
|
'LI',
|
||||||
|
'Link',
|
||||||
|
'Map',
|
||||||
|
'Media',
|
||||||
|
'Menu',
|
||||||
|
'MenuItem',
|
||||||
|
'Meta',
|
||||||
|
'Meter',
|
||||||
|
'Object',
|
||||||
|
'OList',
|
||||||
|
'OptGroup',
|
||||||
|
'Option',
|
||||||
|
'Output',
|
||||||
|
'Paragraph',
|
||||||
|
'Param',
|
||||||
|
'Picture',
|
||||||
|
'Pre',
|
||||||
|
'Progress',
|
||||||
|
'Quote',
|
||||||
|
'Script',
|
||||||
|
'Select',
|
||||||
|
'Slot',
|
||||||
|
'Source',
|
||||||
|
'Span',
|
||||||
|
'Style',
|
||||||
|
'TableCaption',
|
||||||
|
'Table',
|
||||||
|
'TableRow',
|
||||||
|
'TableSection',
|
||||||
|
'Template',
|
||||||
|
'TextArea',
|
||||||
|
'Time',
|
||||||
|
'Title',
|
||||||
|
'Track',
|
||||||
|
'UList',
|
||||||
|
'Unknown',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var i = 0; i < htmlElementSubclasses.length; i++) {
|
||||||
|
var ctor = window['HTML' + htmlElementSubclasses[i] + 'Element'];
|
||||||
|
if (ctor) {
|
||||||
|
ctor.prototype.__proto__ = win.HTMLElement.prototype;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// patch doc.createElement
|
||||||
|
|
||||||
|
var rawCreateElement = doc.createElement;
|
||||||
|
doc._createElement = function(tagName, callConstructor) {
|
||||||
|
var customElements = win['customElements'];
|
||||||
|
var element = rawCreateElement.call(doc, tagName);
|
||||||
|
var definition = customElements._definitions.get(tagName.toLowerCase());
|
||||||
|
if (definition) {
|
||||||
|
customElements._upgradeElement(element, definition, callConstructor);
|
||||||
|
}
|
||||||
|
customElements._observeRoot(element);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
doc.createElement = function(tagName) {
|
||||||
|
return doc._createElement(tagName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// patch doc.createElementNS
|
||||||
|
|
||||||
|
var HTMLNS = 'http://www.w3.org/1999/xhtml';
|
||||||
|
var _origCreateElementNS = doc.createElementNS;
|
||||||
|
doc.createElementNS = function(namespaceURI, qualifiedName) {
|
||||||
|
if (namespaceURI === 'http://www.w3.org/1999/xhtml') {
|
||||||
|
return doc.createElement(qualifiedName);
|
||||||
|
} else {
|
||||||
|
return _origCreateElementNS.call(document, namespaceURI, qualifiedName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// patch Element.attachShadow
|
||||||
|
|
||||||
|
var _origAttachShadow = Element.prototype['attachShadow'];
|
||||||
|
if (_origAttachShadow) {
|
||||||
|
Object.defineProperty(Element.prototype, 'attachShadow', {
|
||||||
|
value: function(options) {
|
||||||
|
var root = _origAttachShadow.call(this, options);
|
||||||
|
var customElements = win['customElements'];
|
||||||
|
customElements._observeRoot(root);
|
||||||
|
return root;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {CustomElementsRegistry} */
|
||||||
|
window['customElements'] = new CustomElementsRegistry();
|
||||||
|
})();
|
Loading…
Reference in New Issue