diff --git a/javascript/atoms/BUILD.bazel b/javascript/atoms/BUILD.bazel
index b6b787107fb39..d780f4b84e44c 100644
--- a/javascript/atoms/BUILD.bazel
+++ b/javascript/atoms/BUILD.bazel
@@ -1,8 +1,62 @@
+load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_run_binary")
load("@rules_closure//closure:defs.bzl", "closure_js_library")
load("//javascript:defs.bzl", "closure_js_deps", "closure_test_suite")
package(default_visibility = ["//visibility:public"])
+js_binary(
+ name = "strip_trailing_semicolon",
+ data = ["typescript/strip-trailing-semicolon.js"],
+ entry_point = "typescript/strip-trailing-semicolon.js",
+)
+
+js_binary(
+ name = "wrap_as_global",
+ data = ["typescript/wrap-as-global.js"],
+ entry_point = "typescript/wrap-as-global.js",
+)
+
+js_run_binary(
+ name = "is-displayed-typescript-compiled",
+ srcs = ["typescript/is-displayed.ts"],
+ outs = ["typescript/is-displayed.compiled.js"],
+ args = [
+ "--target",
+ "ES2017",
+ "--module",
+ "none",
+ "--removeComments",
+ "--pretty",
+ "false",
+ "--outFile",
+ "$(rootpath :typescript/is-displayed.compiled.js)",
+ "$(rootpath typescript/is-displayed.ts)",
+ ],
+ tool = "@npm_typescript//:tsc",
+)
+
+js_run_binary(
+ name = "is-displayed-typescript-generated",
+ srcs = [":is-displayed-typescript-compiled"],
+ outs = ["typescript/is-displayed.generated.js"],
+ args = [
+ "$(rootpath :is-displayed-typescript-compiled)",
+ "$(rootpath :typescript/is-displayed.generated.js)",
+ ],
+ tool = ":strip_trailing_semicolon",
+)
+
+js_run_binary(
+ name = "is-displayed-global",
+ srcs = [":is-displayed-typescript-compiled"],
+ outs = ["typescript/is-displayed-global.js"],
+ args = [
+ "$(rootpath :is-displayed-typescript-compiled)",
+ "$(rootpath :typescript/is-displayed-global.js)",
+ ],
+ tool = ":wrap_as_global",
+)
+
filegroup(
name = "atoms",
srcs = glob([
@@ -11,7 +65,8 @@ filegroup(
"**/*.js",
"**/*.png",
"**/*.svg",
- ]),
+ "**/*.ts",
+ ]) + [":is-displayed-global"],
visibility = [
"//dotnet/test:__subpackages__",
"//java/test/org/openqa/selenium/environment:__pkg__",
diff --git a/javascript/atoms/fragments/BUILD.bazel b/javascript/atoms/fragments/BUILD.bazel
index 2bbe6e6678fc2..5c23b9db952c0 100644
--- a/javascript/atoms/fragments/BUILD.bazel
+++ b/javascript/atoms/fragments/BUILD.bazel
@@ -1,3 +1,4 @@
+load("//common:defs.bzl", "copy_file")
load("//javascript:defs.bzl", "closure_fragment")
closure_fragment(
@@ -130,6 +131,21 @@ closure_fragment(
],
)
+copy_file(
+ name = "is-displayed-typescript",
+ src = "//javascript/atoms:typescript/is-displayed.generated.js",
+ out = "is-displayed-typescript.js",
+ visibility = [
+ "//dotnet/src/webdriver:__pkg__",
+ "//java/src/org/openqa/selenium/remote:__pkg__",
+ "//javascript/chrome-driver:__pkg__",
+ "//javascript/ie-driver:__pkg__",
+ "//javascript/selenium-webdriver/lib/atoms:__pkg__",
+ "//py:__pkg__",
+ "//rb/lib/selenium/webdriver/atoms:__pkg__",
+ ],
+)
+
closure_fragment(
name = "is-editable",
function = "bot.dom.isEditable",
diff --git a/javascript/atoms/test/html5/shadow_dom_typescript_test.html b/javascript/atoms/test/html5/shadow_dom_typescript_test.html
new file mode 100644
index 0000000000000..cd31886bd2cf0
--- /dev/null
+++ b/javascript/atoms/test/html5/shadow_dom_typescript_test.html
@@ -0,0 +1,230 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Page for Shadow DOM tests
+
+ Foo
+
+
+
+
+
+
+
+ Foo
+
+ Foo
+
+
+
+
+
diff --git a/javascript/atoms/test/shown_typescript_test.html b/javascript/atoms/test/shown_typescript_test.html
new file mode 100644
index 0000000000000..d0f11e5a3c412
--- /dev/null
+++ b/javascript/atoms/test/shown_typescript_test.html
@@ -0,0 +1,577 @@
+
+
+
+
+
+ shown_typescript_test.html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Displayed
+
+
Transparent
+
+
+
+
Display set to none
+
+
Hidden
+
+
+
+
+
+
+ Check box you can't see
+
+
+
+
+ cheese puffs
+
+
+
A
+ sub-element that is
+ explicitly visible using CSS visibility=visible
+
+
+
+
+
+
+
+
+ A paragraph suppressed using CSS display=none
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text
+
+
+
+
+
+
+
+
+
+ This is a zero height/width div and overflow hidden
+
+
+
+
+ This is a zero height/width div and overflow hidden
+
+
+
+
+
+
+
+
+ positivePositionChildInNegativePositionParent
+
+
+ This is not visible
+
+
+
+
+
+
+
+ | a |
+
+
+ | b |
+
+
+ | c |
+
+
+
+
+ Some Summary
+
+
+
+
+
+
+
+
+
+
diff --git a/javascript/atoms/typescript/is-displayed.ts b/javascript/atoms/typescript/is-displayed.ts
new file mode 100644
index 0000000000000..422a6742ff90a
--- /dev/null
+++ b/javascript/atoms/typescript/is-displayed.ts
@@ -0,0 +1,482 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+type OverflowState = 'none' | 'hidden' | 'scroll';
+
+interface Rect {
+ left: number;
+ top: number;
+ right: number;
+ bottom: number;
+ width: number;
+ height: number;
+}
+
+interface ImageMapResult {
+ image: Element | null;
+ rect: Rect;
+}
+
+interface OverflowStyles {
+ x: string;
+ y: string;
+}
+
+interface Coordinate {
+ x: number;
+ y: number;
+}
+
+(function (): (elem: Element, optIgnoreOpacity?: boolean) => boolean {
+ function toUpperCaseTag(tagName?: string): string | undefined {
+ return tagName ? tagName.toUpperCase() : undefined;
+ }
+
+ function isElement(node: unknown, tagName?: string): node is Element {
+ if (!node || !(node instanceof Element)) {
+ return false;
+ }
+ var normalizedTagName = toUpperCaseTag(tagName);
+ if (node instanceof HTMLFormElement) {
+ return !normalizedTagName || normalizedTagName === 'FORM';
+ }
+ return (
+ typeof node.tagName === 'string' &&
+ (!normalizedTagName || node.tagName.toUpperCase() === normalizedTagName)
+ );
+ }
+
+ function getParentElement(node: Node): Element | null {
+ var current = node.parentNode;
+ while (
+ current &&
+ current.nodeType !== Node.ELEMENT_NODE &&
+ current.nodeType !== Node.DOCUMENT_NODE &&
+ current.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
+ ) {
+ current = current.parentNode;
+ }
+ return current instanceof Element ? current : null;
+ }
+
+ function getEffectiveStyle(elem: Element, propertyName: string): string | null {
+ var win = elem.ownerDocument.defaultView;
+ if (!win) {
+ return null;
+ }
+ var computed = win.getComputedStyle(elem);
+ if (!computed) {
+ return null;
+ }
+ var value = computed.getPropertyValue(propertyName);
+ return value || null;
+ }
+
+ function getOpacity(elem: Element): number {
+ var opacityStyle = getEffectiveStyle(elem, 'opacity');
+ var opacity = opacityStyle ? Number(opacityStyle) : 1;
+ var parent = getParentElement(elem);
+ return parent ? opacity * getOpacity(parent) : opacity;
+ }
+
+ function getParentNodeInComposedDom(node: Node): Node | null {
+ var parent = node.parentNode;
+ var slottable = node as Node & { assignedSlot?: HTMLSlotElement | null };
+ var parentWithShadow = parent as (Node & { shadowRoot?: ShadowRoot | null }) | null;
+
+ if (parentWithShadow && parentWithShadow.shadowRoot && slottable.assignedSlot !== undefined) {
+ return slottable.assignedSlot ? slottable.assignedSlot.parentNode : null;
+ }
+
+ return parent;
+ }
+
+ function createRect(left: number, top: number, width: number, height: number): Rect {
+ return {
+ left: left,
+ top: top,
+ right: left + width,
+ bottom: top + height,
+ width: width,
+ height: height,
+ };
+ }
+
+ function getClientRect(elem: Element): Rect {
+ var imageMap = maybeFindImageMap(elem);
+ if (imageMap) {
+ return imageMap.rect;
+ }
+
+ var elemTagName = typeof (elem as Element).tagName === 'string' ? (elem as Element).tagName : '';
+ if (elemTagName.toUpperCase() === 'HTML') {
+ var doc = (elem as Element).ownerDocument;
+ return createRect(0, 0, doc.documentElement.clientWidth, doc.documentElement.clientHeight);
+ }
+
+ try {
+ var nativeRect = (elem as Element).getBoundingClientRect();
+ return {
+ left: nativeRect.left,
+ top: nativeRect.top,
+ right: nativeRect.right,
+ bottom: nativeRect.bottom,
+ width: nativeRect.right - nativeRect.left,
+ height: nativeRect.bottom - nativeRect.top,
+ };
+ } catch (_error) {
+ return createRect(0, 0, 0, 0);
+ }
+ }
+
+ function getAreaRelativeRect(area: HTMLAreaElement): Rect {
+ var shape = area.shape.toLowerCase();
+ var coords = area.coords.split(',').map(function (value) {
+ return Number(value.trim());
+ });
+
+ if (shape === 'rect' && coords.length === 4) {
+ return createRect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]);
+ }
+
+ if (shape === 'circle' && coords.length === 3) {
+ return createRect(coords[0] - coords[2], coords[1] - coords[2], coords[2] * 2, coords[2] * 2);
+ }
+
+ if (shape === 'poly' && coords.length > 2) {
+ var minX = coords[0];
+ var minY = coords[1];
+ var maxX = minX;
+ var maxY = minY;
+
+ for (var index = 2; index + 1 < coords.length; index += 2) {
+ minX = Math.min(minX, coords[index]);
+ maxX = Math.max(maxX, coords[index]);
+ minY = Math.min(minY, coords[index + 1]);
+ maxY = Math.max(maxY, coords[index + 1]);
+ }
+
+ return createRect(minX, minY, maxX - minX, maxY - minY);
+ }
+
+ return createRect(0, 0, 0, 0);
+ }
+
+ function findImageUsingMap(mapName: string, doc: Document): Element | null {
+ var elements = doc.getElementsByTagName('*');
+ for (var index = 0; index < elements.length; index += 1) {
+ var useMap = elements[index].getAttribute('usemap');
+ if (useMap === '#' + mapName) {
+ return elements[index];
+ }
+ }
+ return null;
+ }
+
+ function maybeFindImageMap(elem: Element): ImageMapResult | null {
+ var isMap = isElement(elem, 'MAP');
+ if (!isMap && !isElement(elem, 'AREA')) {
+ return null;
+ }
+
+ var map = isMap ? elem : isElement(elem.parentNode, 'MAP') ? elem.parentNode : null;
+ var image: Element | null = null;
+ var rect = createRect(0, 0, 0, 0);
+
+ if (map instanceof HTMLMapElement && map.name) {
+ image = findImageUsingMap(map.name, map.ownerDocument);
+ if (image) {
+ rect = getClientRect(image);
+ if (!isMap && elem instanceof HTMLAreaElement && elem.shape.toLowerCase() !== 'default') {
+ var relativeRect = getAreaRelativeRect(elem);
+ var relativeX = Math.min(Math.max(relativeRect.left, 0), rect.width);
+ var relativeY = Math.min(Math.max(relativeRect.top, 0), rect.height);
+ var width = Math.min(relativeRect.width, rect.width - relativeX);
+ var height = Math.min(relativeRect.height, rect.height - relativeY);
+ rect = createRect(relativeX + rect.left, relativeY + rect.top, width, height);
+ }
+ }
+ }
+
+ return { image: image, rect: rect };
+ }
+
+ function getClientRegion(elem: Element): Rect {
+ return getClientRect(elem);
+ }
+
+ function getOverflowState(elem: Element): OverflowState {
+ var region = getClientRegion(elem);
+ var ownerDoc = elem.ownerDocument;
+ var htmlElem = ownerDoc.documentElement;
+ var bodyElem = ownerDoc.body;
+ var htmlOverflowStyle = getEffectiveStyle(htmlElem, 'overflow') || 'visible';
+ var treatAsFixedPosition = false;
+
+ function canBeOverflowed(container: Element, position: string | null): boolean {
+ if (container === htmlElem) {
+ return true;
+ }
+
+ var display = getEffectiveStyle(container, 'display') || '';
+ if (display.indexOf('inline') === 0 || display === 'contents') {
+ return false;
+ }
+
+ if (position === 'absolute' && getEffectiveStyle(container, 'position') === 'static') {
+ return false;
+ }
+
+ return true;
+ }
+
+ function getOverflowParent(current: Element): Element | null {
+ var position = getEffectiveStyle(current, 'position');
+ if (position === 'fixed') {
+ treatAsFixedPosition = true;
+ return current === htmlElem ? null : htmlElem;
+ }
+
+ var parent = getParentElement(current);
+ while (parent && !canBeOverflowed(parent, position)) {
+ parent = getParentElement(parent);
+ }
+ return parent;
+ }
+
+ function getOverflowStyles(current: Element): OverflowStyles {
+ var overflowElem = current;
+ if (htmlOverflowStyle === 'visible') {
+ if (current === htmlElem && bodyElem) {
+ overflowElem = bodyElem;
+ } else if (current === bodyElem) {
+ return { x: 'visible', y: 'visible' };
+ }
+ }
+
+ var overflow = {
+ x: getEffectiveStyle(overflowElem, 'overflow-x') || 'visible',
+ y: getEffectiveStyle(overflowElem, 'overflow-y') || 'visible',
+ };
+
+ if (current === htmlElem) {
+ overflow.x = overflow.x === 'visible' ? 'auto' : overflow.x;
+ overflow.y = overflow.y === 'visible' ? 'auto' : overflow.y;
+ }
+
+ return overflow;
+ }
+
+ function getScroll(current: Element): Coordinate {
+ if (current === htmlElem) {
+ return {
+ x: ownerDoc.defaultView ? ownerDoc.defaultView.pageXOffset : 0,
+ y: ownerDoc.defaultView ? ownerDoc.defaultView.pageYOffset : 0,
+ };
+ }
+
+ return { x: current.scrollLeft, y: current.scrollTop };
+ }
+
+ for (var container = getOverflowParent(elem); container; container = getOverflowParent(container)) {
+ var containerOverflow = getOverflowStyles(container);
+ if (containerOverflow.x === 'visible' && containerOverflow.y === 'visible') {
+ continue;
+ }
+
+ var containerRect = getClientRect(container);
+ if (containerRect.width === 0 || containerRect.height === 0) {
+ return 'hidden';
+ }
+
+ var underflowsX = region.right < containerRect.left;
+ var underflowsY = region.bottom < containerRect.top;
+ if ((underflowsX && containerOverflow.x === 'hidden') || (underflowsY && containerOverflow.y === 'hidden')) {
+ return 'hidden';
+ }
+
+ if ((underflowsX && containerOverflow.x !== 'visible') || (underflowsY && containerOverflow.y !== 'visible')) {
+ var containerScroll = getScroll(container);
+ var unscrollableX = region.right < containerRect.left - containerScroll.x;
+ var unscrollableY = region.bottom < containerRect.top - containerScroll.y;
+ if ((unscrollableX && containerOverflow.x !== 'visible') || (unscrollableY && containerOverflow.y !== 'visible')) {
+ return 'hidden';
+ }
+
+ var containerUnderflowState = getOverflowState(container);
+ return containerUnderflowState === 'hidden' ? 'hidden' : 'scroll';
+ }
+
+ var overflowsX = region.left >= containerRect.left + containerRect.width;
+ var overflowsY = region.top >= containerRect.top + containerRect.height;
+ if ((overflowsX && containerOverflow.x === 'hidden') || (overflowsY && containerOverflow.y === 'hidden')) {
+ return 'hidden';
+ }
+
+ if ((overflowsX && containerOverflow.x !== 'visible') || (overflowsY && containerOverflow.y !== 'visible')) {
+ if (treatAsFixedPosition) {
+ var docScroll = getScroll(container);
+ if (region.left >= htmlElem.scrollWidth - docScroll.x || region.right >= htmlElem.scrollHeight - docScroll.y) {
+ return 'hidden';
+ }
+ }
+
+ var containerOverflowState = getOverflowState(container);
+ return containerOverflowState === 'hidden' ? 'hidden' : 'scroll';
+ }
+ }
+
+ return 'none';
+ }
+
+ function isShownInternal(elem: Element, ignoreOpacity: boolean, displayedFn: (element: Node) => boolean): boolean {
+ if (!isElement(elem)) {
+ throw new Error('Argument to isShown must be of type Element');
+ }
+
+ if (isElement(elem, 'BODY')) {
+ return true;
+ }
+
+ if (isElement(elem, 'OPTION') || isElement(elem, 'OPTGROUP')) {
+ var select = (elem as Element).closest('select');
+ return !!select && isShownInternal(select, true, displayedFn);
+ }
+
+ var imageMap = maybeFindImageMap(elem);
+ if (imageMap) {
+ return !!imageMap.image && imageMap.rect.width > 0 && imageMap.rect.height > 0 &&
+ isShownInternal(imageMap.image, ignoreOpacity, displayedFn);
+ }
+
+ if (isElement(elem, 'INPUT') && (elem as HTMLInputElement).type.toLowerCase() === 'hidden') {
+ return false;
+ }
+
+ if (isElement(elem, 'NOSCRIPT')) {
+ return false;
+ }
+
+ var visibility = getEffectiveStyle(elem, 'visibility');
+ if (visibility === 'collapse' || visibility === 'hidden') {
+ return false;
+ }
+
+ if (!displayedFn(elem)) {
+ return false;
+ }
+
+ if (!ignoreOpacity && getOpacity(elem) === 0) {
+ return false;
+ }
+
+ function positiveSize(element: Element): boolean {
+ var rect = getClientRect(element);
+ if (rect.height > 0 && rect.width > 0) {
+ return true;
+ }
+
+ if (isElement(element, 'PATH') && (rect.height > 0 || rect.width > 0)) {
+ var strokeWidth = getEffectiveStyle(element, 'stroke-width');
+ return !!strokeWidth && parseInt(strokeWidth, 10) > 0;
+ }
+
+ var elementVisibility = getEffectiveStyle(element, 'visibility');
+ if (elementVisibility === 'collapse' || elementVisibility === 'hidden') {
+ return false;
+ }
+
+ if (!displayedFn(element)) {
+ return false;
+ }
+
+ if (getEffectiveStyle(element, 'overflow') === 'hidden') {
+ return false;
+ }
+
+ for (var index = 0; index < element.childNodes.length; index += 1) {
+ var child = element.childNodes[index];
+ if (child.nodeType === Node.TEXT_NODE) {
+ var text = child.nodeValue || '';
+ if (/^[\s]*$/.test(text) && /[\n\r\t]/.test(text)) {
+ continue;
+ }
+ return true;
+ }
+
+ if (child instanceof Element && positiveSize(child)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (!positiveSize(elem)) {
+ return false;
+ }
+
+ function hiddenByOverflow(element: Element): boolean {
+ if (getOverflowState(element) !== 'hidden') {
+ return false;
+ }
+
+ for (var index = 0; index < element.childNodes.length; index += 1) {
+ var child = element.childNodes[index];
+ if (child instanceof Element && !hiddenByOverflow(child) && positiveSize(child)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return !hiddenByOverflow(elem);
+ }
+
+ return function isShownElement(elem: Element, optIgnoreOpacity?: boolean): boolean {
+ function displayed(node: Node): boolean {
+ if (isElement(node)) {
+ var display = getEffectiveStyle(node, 'display');
+ var contentVisibility = getEffectiveStyle(node, 'content-visibility');
+ if (display === 'none' || contentVisibility === 'hidden') {
+ return false;
+ }
+ }
+
+ var parent = getParentNodeInComposedDom(node);
+ if (typeof ShadowRoot === 'function' && parent instanceof ShadowRoot) {
+ if (parent.host.shadowRoot && parent.host.shadowRoot !== parent) {
+ return false;
+ }
+ parent = parent.host;
+ }
+
+ if (parent && (parent.nodeType === Node.DOCUMENT_NODE || parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE)) {
+ return true;
+ }
+
+ if (parent instanceof HTMLDetailsElement && !parent.open && !isElement(node, 'SUMMARY')) {
+ return false;
+ }
+
+ return !!parent && displayed(parent);
+ }
+
+ return isShownInternal(elem, !!optIgnoreOpacity, displayed);
+ };
+})();
\ No newline at end of file
diff --git a/javascript/atoms/typescript/strip-trailing-semicolon.js b/javascript/atoms/typescript/strip-trailing-semicolon.js
new file mode 100644
index 0000000000000..249b06b4ef074
--- /dev/null
+++ b/javascript/atoms/typescript/strip-trailing-semicolon.js
@@ -0,0 +1,28 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+const fs = require('node:fs');
+
+const [inputPath, outputPath] = process.argv.slice(2);
+
+if (!inputPath || !outputPath) {
+ throw new Error('Expected input and output file paths');
+}
+
+const input = fs.readFileSync(inputPath, 'utf8');
+const output = input.replace(/;\s*$/, '');
+fs.writeFileSync(outputPath, output);
diff --git a/javascript/atoms/typescript/wrap-as-global.js b/javascript/atoms/typescript/wrap-as-global.js
new file mode 100644
index 0000000000000..a176c562b1cc9
--- /dev/null
+++ b/javascript/atoms/typescript/wrap-as-global.js
@@ -0,0 +1,66 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// Wraps the compiled isShown TypeScript output as a browser global so that
+// test HTML pages can load it directly via