Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Launch Custom Elements v1 Polyfill #25141

Merged
merged 10 commits into from Oct 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion build-system/compile/compile.js
Expand Up @@ -103,7 +103,6 @@ function cleanupBuildDir() {
del.sync('build/fake-module');
del.sync('build/patched-module');
del.sync('build/parsers');
fs.mkdirsSync('build/patched-module/document-register-element/build');
fs.mkdirsSync('build/fake-module/third_party/babel');
fs.mkdirsSync('build/fake-module/src/polyfills/');
fs.mkdirsSync('build/fake-polyfills/src/polyfills');
Expand Down
38 changes: 6 additions & 32 deletions build-system/compile/shorten-license.js
Expand Up @@ -20,32 +20,6 @@ const pumpify = require('pumpify');
const replace = require('gulp-regexp-sourcemaps');

/* eslint-disable */
const MIT_FULL = [
'Permission is hereby granted, free of charge, to any person obtaining a copy',
'of this software and associated documentation files (the "Software"), to deal',
'in the Software without restriction, including without limitation the rights',
'to use, copy, modify, merge, publish, distribute, sublicense, and/or sell',
'copies of the Software, and to permit persons to whom the Software is',
'furnished to do so, subject to the following conditions:',
'',
'The above copyright notice and this permission notice shall be included in',
'all copies or substantial portions of the Software.',
'',
'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR',
'IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,',
'FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE',
'AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER',
'LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,',
'OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN',
'THE SOFTWARE.'
].join('\n');

const MIT_SHORT = [
'Use of this source code is governed by a MIT-style',
'license that can be found in the LICENSE file or at',
'https://opensource.org/licenses/MIT.'
].join('\n');

const POLYMER_BSD_FULL = [
'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',
Expand All @@ -62,13 +36,9 @@ const BSD_SHORT = [

/* eslint-enable */

const LICENSES = [[MIT_FULL, MIT_SHORT], [POLYMER_BSD_FULL, BSD_SHORT]];
const LICENSES = [[POLYMER_BSD_FULL, BSD_SHORT]];

const PATHS = [
'third_party/webcomponentsjs/ShadowCSS.js',
'node_modules/document-register-element/build/' +
'document-register-element.patched.js',
];
const PATHS = ['third_party/webcomponentsjs/ShadowCSS.js'];

/**
* We can replace full-text of standard licenses with a pre-approved shorten
Expand All @@ -81,6 +51,10 @@ exports.shortenLicense = function() {
return replace(regex, tuple[1], 'shorten-license');
});

// Pumpify requires at least 2 streams
if (streams.length === 1) {
return streams[0];
}
return pumpify.obj(streams);
};

Expand Down
2 changes: 0 additions & 2 deletions build-system/compile/sources.js
Expand Up @@ -46,8 +46,6 @@ const COMMON_GLOBS = [
'node_modules/web-activities/activity-ports.js',
'node_modules/@ampproject/animations/dist/animations.mjs',
'node_modules/@ampproject/worker-dom/dist/amp/main.mjs',
'node_modules/document-register-element/build/' +
'document-register-element.patched.js',
];

/**
Expand Down
9 changes: 1 addition & 8 deletions build-system/global-configs/experiments-config.json
Expand Up @@ -15,12 +15,5 @@
"expirationDateUTC": "2019-11-10",
"defineExperimentConstant": "_RTVEXP_INABOX_LITE"
},
"experimentC": {
"name": "Custom Elements V1",
"environment": "AMP",
"command": "gulp dist --defineExperimentConstant=CUSTOM_ELEMENTS_V1",
"issue": "https://github.com/ampproject/amphtml/issues/17243",
"expirationDateUTC": "2020-01-01",
"defineExperimentConstant": "CUSTOM_ELEMENTS_V1"
}
"experimentC": {}
}
4 changes: 0 additions & 4 deletions build-system/tasks/presubmit-checks.js
Expand Up @@ -85,10 +85,6 @@ const forbiddenTerms = {
'https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5 ' +
'for a list of alternatives.',
},
'document-register-element.node': {
message: 'Use `document-register-element.patched` instead',
whitelist: ['build-system/tasks/update-packages.js'],
},
'sinon\\.(spy|stub|mock)\\(': {
message: 'Use a sandbox instead to avoid repeated `#restore` calls',
},
Expand Down
43 changes: 1 addition & 42 deletions build-system/tasks/update-packages.js
Expand Up @@ -37,24 +37,6 @@ function writeIfUpdated(patchedName, file) {
}
}

/**
* @param {string} filePath
* @param {string} newFilePath
* @param {...any} args Search and replace string pairs.
*/
function replaceInFile(filePath, newFilePath, ...args) {
let file = fs.readFileSync(filePath, 'utf8');
for (let i = 0; i < args.length; i += 2) {
const searchValue = args[i];
const replaceValue = args[i + 1];
if (!file.includes(searchValue)) {
throw new Error(`Expected "${searchValue}" to appear in ${filePath}.`);
}
file = file.replace(searchValue, replaceValue);
}
writeIfUpdated(newFilePath, file);
}

/**
* Patches Web Animations API by wrapping its body into `install` function.
* This gives us an option to call polyfill directly on the main window
Expand Down Expand Up @@ -94,28 +76,6 @@ function patchWebAnimations() {
writeIfUpdated(patchedName, file);
}

/**
* Creates a version of document-register-element that can be installed
* without side effects.
*/
function patchRegisterElement() {
// Copies document-register-element into a new file that has an export.
// This works around a bug in closure compiler, where without the
// export this module does not generate a goog.provide which fails
// compilation: https://github.com/google/closure-compiler/issues/1831
const dir = 'node_modules/document-register-element/build/';
replaceInFile(
dir + 'document-register-element.node.js',
dir + 'document-register-element.patched.js',
// Elimate the immediate side effect.
'installCustomElements(global);',
'',
// Replace CJS export with ES6 export.
'module.exports = installCustomElements;',
'export {installCustomElements};'
);
}

/**
* Does a yarn check on node_modules, and if it is outdated, runs yarn.
*/
Expand Down Expand Up @@ -158,14 +118,13 @@ function maybeUpdatePackages() {

/**
* Installs custom lint rules, updates node_modules (for local dev), and patches
* web-animations-js and document-register-element if necessary.
* web-animations-js if necessary.
*/
async function updatePackages() {
if (!isTravisBuild()) {
runYarnCheck();
}
patchWebAnimations();
patchRegisterElement();
}

module.exports = {
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -20,7 +20,6 @@
"dependencies": {
"@ampproject/animations": "0.2.0",
"@ampproject/worker-dom": "0.21.0",
"document-register-element": "1.5.0",
"dompurify": "2.0.2",
"moment": "2.24.0",
"preact": "8.4.2",
Expand Down
12 changes: 1 addition & 11 deletions src/friendly-iframe-embed.js
Expand Up @@ -37,12 +37,10 @@ import {
} from './service';
import {escapeHtml} from './dom';
import {getExperimentBranch, isExperimentOn} from './experiments';
import {getMode} from './mode';
import {installAmpdocServices} from './service/core-services';
import {install as installCustomElements} from './polyfills/custom-elements';
import {install as installDOMTokenList} from './polyfills/domtokenlist';
import {install as installDocContains} from './polyfills/document-contains';
import {installCustomElements as installRegisterElement} from 'document-register-element/build/document-register-element.patched';
import {installStylesForDoc, installStylesLegacy} from './style-installer';
import {installTimerInEmbedWindow} from './service/timer-impl';
import {isDocumentReady} from './document-ready';
Expand Down Expand Up @@ -857,15 +855,7 @@ export class FriendlyIframeEmbed {
function installPolyfillsInChildWindow(parentWin, childWin) {
installDocContains(childWin);
installDOMTokenList(childWin);
if (
// eslint-disable-next-line no-undef
CUSTOM_ELEMENTS_V1 ||
getMode().test
) {
installCustomElements(childWin);
} else {
installRegisterElement(childWin, 'auto');
}
installCustomElements(childWin);
}

/**
Expand Down
14 changes: 1 addition & 13 deletions src/polyfills.js
Expand Up @@ -16,7 +16,6 @@

/** @fileoverview */

import {getMode} from './mode';
import {install as installArrayIncludes} from './polyfills/array-includes';
import {install as installCustomElements} from './polyfills/custom-elements';
import {install as installDOMTokenList} from './polyfills/domtokenlist';
Expand All @@ -27,7 +26,6 @@ import {install as installMathSign} from './polyfills/math-sign';
import {install as installObjectAssign} from './polyfills/object-assign';
import {install as installObjectValues} from './polyfills/object-values';
import {install as installPromise} from './polyfills/promise';
import {installCustomElements as installRegisterElement} from 'document-register-element/build/document-register-element.patched';

installFetch(self);
installMathSign(self);
Expand All @@ -41,17 +39,7 @@ if (self.document) {
installDOMTokenList(self);
installDocContains(self);
installGetBoundingClientRect(self);

// TODO(jridgewell, estherkim): Find out why CE isn't being polyfilled for IE.
if (
// eslint-disable-next-line no-undef
CUSTOM_ELEMENTS_V1 ||
(getMode().test && !getMode().testIe)
) {
installCustomElements(self);
} else {
installRegisterElement(self, 'auto');
}
installCustomElements(self);
}

// TODO(#18268, erwinm): For whatever reason imports to modules that have no
Expand Down
23 changes: 19 additions & 4 deletions src/polyfills/custom-elements.js
Expand Up @@ -672,13 +672,27 @@ function installPatches(win, registry) {
// Patch the innerHTML setter to immediately upgrade custom elements.
// Note, this could technically fire connectedCallbacks if this node was
// connected, but we leave that to the Mutation Observer.
const innerHTMLDesc = Object.getOwnPropertyDescriptor(elProto, 'innerHTML');
let innerHTMLProto = elProto;
let innerHTMLDesc = Object.getOwnPropertyDescriptor(
innerHTMLProto,
'innerHTML'
);
if (!innerHTMLDesc) {
// Sigh... IE11 puts innerHTML desciptor on HTMLElement. But, we've
// replaced HTMLElement with a polyfill wrapper, so have to get its proto.
innerHTMLProto =
/** @type {!Object} */ (win.HTMLElement.prototype.__proto__);
innerHTMLDesc = Object.getOwnPropertyDescriptor(
innerHTMLProto,
'innerHTML'
);
}
const innerHTMLSetter = innerHTMLDesc.set;
innerHTMLDesc.set = function(html) {
innerHTMLSetter.call(this, html);
registry.upgrade(this);
};
Object.defineProperty(elProto, 'innerHTML', innerHTMLDesc);
Object.defineProperty(innerHTMLProto, 'innerHTML', innerHTMLDesc);
}

/**
Expand Down Expand Up @@ -852,14 +866,15 @@ function subClass(Object, superClass, subClass) {
export function install(win, opt_ctor) {
// Don't install in no-DOM environments e.g. worker.
const shouldInstall = win.document;
if (!shouldInstall || isPatched(win)) {
const hasCE = hasCustomElements(win);
if (!shouldInstall || (hasCE && isPatched(win))) {
return;
}

let install = true;
let installWrapper = false;

if (opt_ctor && hasCustomElements(win)) {
if (opt_ctor && hasCE) {
// If ctor is constructable without new, it's a function. That means it was
// compiled down, and we need to do the minimal polyfill because all you
// cannot extend HTMLElement without native classes.
Expand Down
10 changes: 1 addition & 9 deletions src/service/custom-element-registry.js
Expand Up @@ -141,15 +141,7 @@ export function registerElement(win, name, implementationClass) {
const knownElements = getExtendedElements(win);
knownElements[name] = implementationClass;
const klass = createCustomElementClass(win, name);

const supportsCustomElementsV1 = 'customElements' in win;
if (supportsCustomElementsV1) {
win['customElements'].define(name, klass);
} else {
win.document.registerElement(name, {
prototype: klass.prototype,
});
}
win['customElements'].define(name, klass);
}

/**
Expand Down