Commit 0ba01cf0 authored by Hiroshige Hayashizaki's avatar Hiroshige Hayashizaki Committed by Commit Bot

[Built-in Modules] Remove Switch Element

As a part of turning down Built-in Modules in Blink.

Bug: 829084, 972476, 1055006
Change-Id: Ibd0fcc327977d1fcd5da811e7f0b245ae9f9f797
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2094493
Commit-Queue: Hiroshige Hayashizaki <hiroshige@chromium.org>
Reviewed-by: default avatarKent Tamura <tkent@chromium.org>
Reviewed-by: default avatarDomenic Denicola <domenic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749585}
parent ed3c2dd1
...@@ -15,9 +15,6 @@ namespace layered_api { ...@@ -15,9 +15,6 @@ namespace layered_api {
enum class Module { enum class Module {
kBlank, kBlank,
kElementsInternal,
kElementsSwitch,
kElementsToast,
kElementsVirtualScroller, kElementsVirtualScroller,
}; };
......
...@@ -29,19 +29,6 @@ struct LayeredAPIResource { ...@@ -29,19 +29,6 @@ struct LayeredAPIResource {
const LayeredAPIResource kLayeredAPIResources[] = { const LayeredAPIResource kLayeredAPIResources[] = {
{"blank/index.mjs", IDR_LAYERED_API_BLANK_INDEX_MJS, Module::kBlank}, {"blank/index.mjs", IDR_LAYERED_API_BLANK_INDEX_MJS, Module::kBlank},
{"elements/internal/reflection.mjs",
IDR_LAYERED_API_ELEMENTS_INTERNAL_REFLECTION_MJS,
Module::kElementsInternal},
{"elements/switch/face_utils.mjs",
IDR_LAYERED_API_ELEMENTS_SWITCH_FACE_UTILS_MJS, Module::kElementsSwitch},
{"elements/switch/index.mjs", IDR_LAYERED_API_ELEMENTS_SWITCH_INDEX_MJS,
Module::kElementsSwitch},
{"elements/switch/style.mjs", IDR_LAYERED_API_ELEMENTS_SWITCH_STYLE_MJS,
Module::kElementsSwitch},
{"elements/switch/track.mjs", IDR_LAYERED_API_ELEMENTS_SWITCH_TRACK_MJS,
Module::kElementsSwitch},
{"elements/virtual-scroller/find-element.mjs", {"elements/virtual-scroller/find-element.mjs",
IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_FIND_ELEMENT_MJS, IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_FIND_ELEMENT_MJS,
Module::kElementsVirtualScroller}, Module::kElementsVirtualScroller},
......
...@@ -73,13 +73,6 @@ bool ModulatorImplBase::BuiltInModuleEnabled(layered_api::Module module) const { ...@@ -73,13 +73,6 @@ bool ModulatorImplBase::BuiltInModuleEnabled(layered_api::Module module) const {
switch (module) { switch (module) {
case layered_api::Module::kBlank: case layered_api::Module::kBlank:
return true; return true;
case layered_api::Module::kElementsInternal:
// Union of conditions of KElementsSwitch and kElementsToast.
return RuntimeEnabledFeatures::BuiltInModuleSwitchElementEnabled();
case layered_api::Module::kElementsSwitch:
return RuntimeEnabledFeatures::BuiltInModuleSwitchElementEnabled();
case layered_api::Module::kElementsToast:
return RuntimeEnabledFeatures::BuiltInModuleAllEnabled();
case layered_api::Module::kElementsVirtualScroller: case layered_api::Module::kElementsVirtualScroller:
return false; return false;
} }
...@@ -89,9 +82,6 @@ bool ModulatorImplBase::BuiltInModuleRequireSecureContext( ...@@ -89,9 +82,6 @@ bool ModulatorImplBase::BuiltInModuleRequireSecureContext(
layered_api::Module module) { layered_api::Module module) {
switch (module) { switch (module) {
case layered_api::Module::kBlank: case layered_api::Module::kBlank:
case layered_api::Module::kElementsInternal:
case layered_api::Module::kElementsSwitch:
case layered_api::Module::kElementsToast:
case layered_api::Module::kElementsVirtualScroller: case layered_api::Module::kElementsVirtualScroller:
return false; return false;
} }
...@@ -104,15 +94,6 @@ void ModulatorImplBase::BuiltInModuleUseCount( ...@@ -104,15 +94,6 @@ void ModulatorImplBase::BuiltInModuleUseCount(
switch (module) { switch (module) {
case layered_api::Module::kBlank: case layered_api::Module::kBlank:
break; break;
case layered_api::Module::kElementsInternal:
break;
case layered_api::Module::kElementsSwitch:
UseCounter::Count(GetExecutionContext(),
WebFeature::kBuiltInModuleSwitchImported);
break;
case layered_api::Module::kElementsToast:
UseCounter::Count(GetExecutionContext(), WebFeature::kBuiltInModuleToast);
break;
case layered_api::Module::kElementsVirtualScroller: case layered_api::Module::kElementsVirtualScroller:
UseCounter::Count(GetExecutionContext(), UseCounter::Count(GetExecutionContext(),
WebFeature::kBuiltInModuleVirtualScroller); WebFeature::kBuiltInModuleVirtualScroller);
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @file Manage attribute-property reflections.
* https://html.spec.whatwg.org/C/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes
*/
/**
* Add a bool reflection property to the specified prototype for the specified
* attribute.
*
* @param {!Object} proto An element prototype
* @param {string} attrName An attribute name
* @param {string} propName An optional property name. attrName will be used if
* this argument is omitted.
*/
export function installBool(proto, attrName, propName = attrName) {
function getter() {
return this.hasAttribute(attrName);
}
function setter(value) {
this.toggleAttribute(attrName, Boolean(value));
}
Object.defineProperty(
getter, 'name',
{configurable: true, enumerable: false, value: 'get ' + propName});
Object.defineProperty(
setter, 'name',
{configurable: true, enumerable: false, value: 'set ' + propName});
Object.defineProperty(
proto, propName,
{configurable: true, enumerable: true, get: getter, set: setter});
}
/**
* Add a DOMString reflection property to the specified prototype for the
* specified attribute.
*
* @param {!Element} element An element prototype
* @param {string} attrName An attribute name
* @param {string} propName An optional property name. attrName will be used if
* this argument is omitted.
*/
export function installString(proto, attrName, propName = attrName) {
function getter() {
const value = this.getAttribute(attrName);
return value === null ? '' : value;
}
function setter(value) {
this.setAttribute(attrName, value);
}
Object.defineProperty(
getter, 'name',
{configurable: true, enumerable: false, value: 'get ' + propName});
Object.defineProperty(
setter, 'name',
{configurable: true, enumerable: false, value: 'set ' + propName});
Object.defineProperty(
proto, propName,
{configurable: true, enumerable: true, get: getter, set: setter});
}
# Switch control element
https://github.com/tkent-google/std-switch/blob/master/README.md
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @file Utilities for form-associated custom elements
*/
import * as reflection from '../internal/reflection.mjs';
function installGetter(proto, propName, getter) {
Object.defineProperty(
getter, 'name',
{configurable: true, enumerable: false, value: 'get ' + propName});
Object.defineProperty(
proto, propName, {configurable: true, enumerable: true, get: getter});
}
/**
* Add the following properties to |proto|
* - disabled
* - name
* - type
* and make the following properties and functions enumerable
* - form
* - willValidate
* - validity
* - validationMessage
* - labels
* - checkValidity()
* - reportValidity()
* - setCustomValidity(error)
*
* @param {!Object} proto An Element prototype which will have properties
*/
export function installProperties(proto) {
reflection.installBool(proto, 'disabled');
reflection.installString(proto, 'name');
installGetter(proto, 'type', function() {
if (!(this instanceof proto.constructor)) {
throw new TypeError(
'The context object is not an instance of ' + proto.contructor.name);
}
return this.localName;
});
Object.defineProperty(proto, 'form', {enumerable: true});
Object.defineProperty(proto, 'willValidate', {enumerable: true});
Object.defineProperty(proto, 'validity', {enumerable: true});
Object.defineProperty(proto, 'validationMessage', {enumerable: true});
Object.defineProperty(proto, 'labels', {enumerable: true});
Object.defineProperty(proto, 'checkValidity', {enumerable: true});
Object.defineProperty(proto, 'reportValidity', {enumerable: true});
Object.defineProperty(proto, 'setCustomValidity', {enumerable: true});
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as reflection from '../internal/reflection.mjs';
import * as face from './face_utils.mjs';
import * as style from './style.mjs';
import {SwitchTrack} from './track.mjs';
const generateStyleSheet = style.styleSheetFactory();
const generateMaterialStyleSheet = style.materialStyleSheetFactory();
// https://github.com/tkent-google/std-switch/issues/2
const STATE_ATTR = 'on';
function parentOrHostElement(element) {
const parent = element.parentNode;
if (!parent) {
return null;
}
if (parent.nodeType === Node.ELEMENT_NODE) {
return parent;
}
if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return parent.host;
}
return null;
}
function shouldUsePlatformTheme(element) {
for (; element; element = parentOrHostElement(element)) {
const themeValue = element.getAttribute('theme');
if (themeValue === 'match-platform') {
return true;
} else if (themeValue === 'platform-agnostic') {
return false;
}
}
return false;
}
export class StdSwitchElement extends HTMLElement {
// TODO(tkent): The following should be |static fooBar = value;|
// after enabling babel-eslint.
static get formAssociated() {
return true;
}
static get observedAttributes() {
return [STATE_ATTR];
}
#internals;
#track;
#containerElement;
#inUserAction = false;
#shadowRoot;
constructor() {
super();
if (new.target !== StdSwitchElement) {
throw new TypeError(
'Illegal constructor: StdSwitchElement is not ' +
'extensible for now');
}
this.#internals = this.attachInternals();
this.#internals.setFormValue('off');
this.#initializeDOM();
this.addEventListener('click', this.#onClick);
this.addEventListener('keypress', this.#onKeyPress);
}
attributeChangedCallback(attrName, oldValue, newValue) {
if (attrName === STATE_ATTR) {
this.#internals.setFormValue(newValue !== null ? 'on' : 'off');
this.#track.value = newValue !== null;
if (this.#internals.ariaChecked !== undefined) {
this.#internals.ariaChecked = newValue !== null ? 'true' : 'false';
} else {
// TODO(tkent): Remove this when we ship AOM.
this.setAttribute('aria-checked', newValue !== null ? 'true' : 'false');
}
if (!this.#inUserAction) {
for (const element of this.#containerElement.querySelectorAll('*')) {
style.unmarkTransition(element);
}
}
}
}
connectedCallback() {
// The element might have been disconnected when the callback is invoked.
if (!this.isConnected) {
return;
}
// TODO(tkent): We should not add tabindex attribute.
// https://github.com/w3c/webcomponents/issues/762
if (!this.hasAttribute('tabindex')) {
this.setAttribute('tabindex', '0');
}
if (this.#internals.role !== undefined) {
this.#internals.role = 'switch';
} else {
// TODO(tkent): Remove this when we ship AOM.
if (!this.hasAttribute('role')) {
this.setAttribute('role', 'switch');
}
}
if (shouldUsePlatformTheme(this)) {
// TODO(tkent): Should we apply Cocoa-like on macOS and Fluent-like
// on Windows?
this.#shadowRoot.adoptedStyleSheets =
[generateStyleSheet(), generateMaterialStyleSheet()];
} else {
this.#shadowRoot.adoptedStyleSheets = [generateStyleSheet()];
}
}
formResetCallback() {
this.on = this.defaultOn;
}
#initializeDOM = () => {
const factory = this.ownerDocument;
const root = this.attachShadow({mode: 'closed'});
this.#containerElement = factory.createElement('span');
this.#containerElement.id = 'container';
// Shadow elements should be invisible for a11y technologies.
this.#containerElement.setAttribute('aria-hidden', 'true');
root.appendChild(this.#containerElement);
this.#track = new SwitchTrack(factory);
this.#containerElement.appendChild(this.#track.element);
this.#track.value = this.on;
const thumbElement =
this.#containerElement.appendChild(factory.createElement('span'));
thumbElement.id = 'thumb';
thumbElement.part.add('thumb');
this.#shadowRoot = root;
};
#onClick = () => {
for (const element of this.#containerElement.querySelectorAll('*')) {
style.markTransition(element);
}
this.#inUserAction = true;
try {
this.on = !this.on;
} finally {
this.#inUserAction = false;
}
this.dispatchEvent(new Event('input', {bubbles: true}));
this.dispatchEvent(new Event('change', {bubbles: true}));
};
#onKeyPress = event => {
if (event.code === 'Space') {
// Do not scroll the page.
event.preventDefault();
this.#onClick(event);
}
};
// -------- Boilerplate code for form-associated custom elements --------
// They can't be in face_utils.mjs because private fields are available
// only in the class.
get form() {
return this.#internals.form;
}
get willValidate() {
return this.#internals.willValidate;
}
get validity() {
return this.#internals.validity;
}
get validationMessage() {
return this.#internals.validationMessage;
}
get labels() {
return this.#internals.labels;
}
checkValidity() {
return this.#internals.checkValidity();
}
reportValidity() {
return this.#internals.reportValidity();
}
setCustomValidity(error) {
if (error === undefined) {
throw new TypeError('Too few arguments');
}
this.#internals.setValidity({customError: true}, error);
}
}
reflection.installBool(StdSwitchElement.prototype, STATE_ATTR);
reflection.installBool(
StdSwitchElement.prototype, 'default' + STATE_ATTR,
'default' + STATE_ATTR.charAt(0).toUpperCase() + STATE_ATTR.substring(1));
face.installProperties(StdSwitchElement.prototype);
// This is necessary for anyObject.toString.call(switchInstance).
Object.defineProperty(StdSwitchElement.prototype, Symbol.toStringTag, {
configurable: true,
enumerable: false,
value: 'StdSwitchElement',
writable: false,
});
customElements.define('std-switch', StdSwitchElement);
delete StdSwitchElement.formAssociated;
delete StdSwitchElement.observedAttributes;
delete StdSwitchElement.prototype.attributeChangedCallback;
delete StdSwitchElement.prototype.connectedCallback;
delete StdSwitchElement.prototype.formResetCallback;
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Style constant values.
const COLOR_ON = '#0077FF';
const TRACK_RADIUS = '13px';
const TRACK_BORDER_WIDTH = '2px';
const THUMB_HEIGHT = '22px';
const THUMB_WIDTH = '22px';
const THUMB_MARGIN_START = '2px';
const THUMB_MARGIN_END = '2px';
const MATERIAL_THUMB_SIZE = '20px';
const RIPPLE_COLOR = 'rgba(100,100,100,0.3)';
const RIPPLE_MAX_SIZE = '48px';
// Returns a function returning a CSSStyleSheet().
// TODO(tkent): Share this stylesheet factory feature with elements/toast/.
export function styleSheetFactory() {
let styleSheet;
return () => {
if (!styleSheet) {
styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(`
:host {
block-size: 26px;
border: none;
box-sizing: border-box;
display: inline-block;
inline-size: 54px;
user-select: none;
vertical-align: middle;
}
#container {
align-items: center;
block-size: 100%;
display: inline-flex;
inline-size: 100%;
}
#thumb {
background: white;
block-size: ${THUMB_HEIGHT};
border-radius: calc(${THUMB_HEIGHT} / 2);
border: 1px solid black;
box-sizing: border-box;
display: inline-block;
margin-inline-start: calc(-100% + ${THUMB_MARGIN_START});
inline-size: ${THUMB_WIDTH};
}
/* :host::part(thumb-transitioning) doesn't work. crbug.com/980506 */
#thumb[part~="thumb-transitioning"] {
transition: all linear 0.1s;
}
:host([on]) #thumb {
border: 1px solid ${COLOR_ON};
margin-inline-start: calc(0px - ${THUMB_WIDTH} - ${THUMB_MARGIN_END});
}
#track {
block-size: 100%;
border-radius: ${TRACK_RADIUS};
border: ${TRACK_BORDER_WIDTH} solid #dddddd;
box-shadow: 0 0 0 1px #f8f8f8;
box-sizing: border-box;
display: inline-block;
inline-size: 100%;
overflow: hidden;
padding: 0px;
}
#trackFill {
background: ${COLOR_ON};
block-size: 100%;
border-radius: calc(${TRACK_RADIUS}) - ${TRACK_BORDER_WIDTH});
box-shadow: none;
box-sizing: border-box;
display: inline-block;
inline-size: 0%;
vertical-align: top;
}
#trackFill[part~="track-fill-transitioning"] {
transition: all linear 0.1s;
}
:host([on]) #track {
border: ${TRACK_BORDER_WIDTH} solid ${COLOR_ON};
}
:host(:focus) {
outline-offset: 4px;
}
:host(:focus) #track {
box-shadow: 0 0 0 2px #f8f8f8;
}
:host([on]:focus) #track {
box-shadow: 0 0 0 2px #dddddd;
}
:host(:focus) #thumb {
border: 2px solid black;
}
:host([on]:focus) #thumb {
border: 2px solid ${COLOR_ON};
}
:host(:not(:focus-visible):focus) {
outline: none;
}
:host(:not(:disabled):hover) #thumb {
inline-size: 26px;
}
:host([on]:not(:disabled):hover) #thumb {
margin-inline-start: calc(0px - 26px - ${THUMB_MARGIN_END});
}
:host(:active) #track {
background: #dddddd;
}
:host([on]:active) #track {
border: 2px solid #77bbff;
box-shadow: 0 0 0 2px #f8f8f8;
}
:host([on]:active) #trackFill {
background: #77bbff;
}
:host(:disabled) {
opacity: 0.38;
}
/*
* display:inline-block in the :host ruleset overrides 'hidden' handling
* by the user agent.
*/
:host([hidden]) {
display: none;
}
`);
}
return styleSheet;
};
}
export function materialStyleSheetFactory() {
let styleSheet;
return () => {
if (!styleSheet) {
styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(`
:host {
block-size: 20px;
inline-size: 36px;
}
#track,
:host(:active) #track {
background: rgba(0,0,0,0.4);
block-size: 14px;
border: none;
box-shadow: none;
}
:host([on]) #track,
:host([on]:active) #track,
:host([on]:focus) #track {
border: none;
box-shadow: none;
}
#trackFill,
:host([on]:active) #trackFill {
background: rgba(63,81,181,0.5);
}
#thumb,
:host(:focus) #thumb {
block-size: ${MATERIAL_THUMB_SIZE};
border-radius: calc(${MATERIAL_THUMB_SIZE} / 2);
border: none;
box-shadow: 0 1px 5px 0 rgba(0,0,0,0.6);
inline-size: ${MATERIAL_THUMB_SIZE};
margin-inline-start: -100%;
}
:host([on]) #thumb,
:host([on]:focus) #thumb,
:host([on]:not(:disabled):hover) #thumb {
background: rgb(63,81,181);
border: none;
margin-inline-start: calc(0px - 20px);
}
:host(:not(:disabled):hover) #thumb {
inline-size: ${MATERIAL_THUMB_SIZE};
}
/*
* Ripple effect
*
* Translucent circle is painted on the thumb if the element is :active or
* :focus-visible. It has
* - Size transition when it appears
* - Opacity transition when it disappears
* part(thumb)::before represents the former, and part(thumb)::after represents
* the latter.
*/
#thumb::before {
background: ${RIPPLE_COLOR};
block-size: 0px;
border-radius: 0px;
content: "";
display: inline-block;
inline-size: 0px;
left: calc(${MATERIAL_THUMB_SIZE} / 2);
position: relative;
top: calc(${MATERIAL_THUMB_SIZE} / 2);
transition: none;
}
:host(:active) #thumb::before,
:host(:focus-visible) #thumb::before {
block-size: ${RIPPLE_MAX_SIZE};
border-radius: calc(${RIPPLE_MAX_SIZE} / 2);
inline-size: ${RIPPLE_MAX_SIZE};
left: calc((${MATERIAL_THUMB_SIZE} - ${RIPPLE_MAX_SIZE}) / 2);
top: calc((${MATERIAL_THUMB_SIZE} - ${RIPPLE_MAX_SIZE}) / 2);
transition: all linear 0.1s;
}
#thumb::after {
background: ${RIPPLE_COLOR};
block-size: ${RIPPLE_MAX_SIZE};
border-radius: calc(${RIPPLE_MAX_SIZE} / 2);
content: "";
display: inline-block;
inline-size: ${RIPPLE_MAX_SIZE};
left: calc((${MATERIAL_THUMB_SIZE} - ${RIPPLE_MAX_SIZE}) / 2);
opacity: 0;
position: relative;
/* Why 18px? */
top: calc((${MATERIAL_THUMB_SIZE} - ${RIPPLE_MAX_SIZE}) / 2 - 18px);
transition: opacity linear 0.3s;
}
:host(:active) #thumb::after,
:host(:focus-visible) #thumb::after {
block-size: 0px;
content: "";
inline-size: 0px;
opacity: 1;
transition: none;
}
`);
}
return styleSheet;
};
}
/**
* Add '$part-transitioning' part to the element if the element already has
* a part name.
*
* TODO(tkent): We should apply custom state.
*
* @param {!Element} element
*/
export function markTransition(element) {
// Should check hasAttribute() to avoid creating a DOMTokenList instance.
if (!element.hasAttribute('part') || element.part.length < 1) {
return;
}
element.part.add(element.part[0] + '-transitioning');
}
/**
* Remove '$part-transitioning' part from the element if the element already has
* a part name.
*
* TODO(tkent): We should apply custom state.
*
* @param {!Element} element
*/
export function unmarkTransition(element) {
// Should check hasAttribute() to avoid creating a DOMTokenList instance.
if (!element.hasAttribute('part') || element.part.length < 1) {
return;
}
element.part.remove(element.part[0] + '-transitioning');
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
export class SwitchTrack {
#value;
#trackElement;
#fillElement;
#slotElement;
/**
* @param {!Document} factory A factory for elements created for this track.
*/
constructor(factory) {
this.#value = false;
this.#initializeDOM(factory);
}
/**
* @return {!Element}
*/
get element() {
return this.#trackElement;
}
/**
* @param {Boolean} newValue
*/
set value(newValue) {
const oldValue = this.#value;
this.#value = Boolean(newValue);
const bar = this.#fillElement;
if (bar) {
bar.style.inlineSize = this.#value ? '100%' : '0%';
if (oldValue !== this.#value) {
this.#addSlot();
}
}
}
/**
* @param {!Document} factory A factory for elements created for this track.
*/
#initializeDOM = factory => {
this.#trackElement = factory.createElement('div');
this.#trackElement.id = 'track';
this.#trackElement.part.add('track');
this.#fillElement = factory.createElement('span');
this.#fillElement.id = 'trackFill';
this.#fillElement.part.add('track-fill');
this.#trackElement.appendChild(this.#fillElement);
this.#slotElement = factory.createElement('slot');
this.#addSlot();
};
/**
* Add the <slot>
* - next to _fillElement if _value is true
* - as a child of _fillElement if _value is false
* This behavior is helpful to show text in the track.
*/
#addSlot = () => {
if (this.#value) {
this.#fillElement.appendChild(this.#slotElement);
} else {
this.#trackElement.appendChild(this.#slotElement);
}
};
}
...@@ -7,11 +7,6 @@ ...@@ -7,11 +7,6 @@
third_party/blink/public/blink_resources.grd. third_party/blink/public/blink_resources.grd.
--> -->
<include name="IDR_LAYERED_API_BLANK_INDEX_MJS" file="../renderer/core/script/resources/layered_api/blank/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> <include name="IDR_LAYERED_API_BLANK_INDEX_MJS" file="../renderer/core/script/resources/layered_api/blank/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/>
<include name="IDR_LAYERED_API_ELEMENTS_INTERNAL_REFLECTION_MJS" file="../renderer/core/script/resources/layered_api/elements/internal/reflection.mjs" type="BINDATA" skip_minify="true" compress="gzip"/>
<include name="IDR_LAYERED_API_ELEMENTS_SWITCH_FACE_UTILS_MJS" file="../renderer/core/script/resources/layered_api/elements/switch/face_utils.mjs" type="BINDATA" skip_minify="true" compress="gzip"/>
<include name="IDR_LAYERED_API_ELEMENTS_SWITCH_INDEX_MJS" file="../renderer/core/script/resources/layered_api/elements/switch/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/>
<include name="IDR_LAYERED_API_ELEMENTS_SWITCH_STYLE_MJS" file="../renderer/core/script/resources/layered_api/elements/switch/style.mjs" type="BINDATA" skip_minify="true" compress="gzip"/>
<include name="IDR_LAYERED_API_ELEMENTS_SWITCH_TRACK_MJS" file="../renderer/core/script/resources/layered_api/elements/switch/track.mjs" type="BINDATA" skip_minify="true" compress="gzip"/>
<include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_FIND_ELEMENT_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/find-element.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> <include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_FIND_ELEMENT_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/find-element.mjs" type="BINDATA" skip_minify="true" compress="gzip"/>
<include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_INDEX_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> <include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_INDEX_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/index.mjs" type="BINDATA" skip_minify="true" compress="gzip"/>
<include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_SETS_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/sets.mjs" type="BINDATA" skip_minify="true" compress="gzip"/> <include name="IDR_LAYERED_API_ELEMENTS_VIRTUAL_SCROLLER_SETS_MJS" file="../renderer/core/script/resources/layered_api/elements/virtual-scroller/sets.mjs" type="BINDATA" skip_minify="true" compress="gzip"/>
......
...@@ -266,12 +266,7 @@ ...@@ -266,12 +266,7 @@
name: "BuiltInModuleInfra", name: "BuiltInModuleInfra",
origin_trial_feature_name: "BuiltInModuleInfra", origin_trial_feature_name: "BuiltInModuleInfra",
implied_by: ["ExperimentalProductivityFeatures", implied_by: ["ExperimentalProductivityFeatures",
"BuiltInModuleAll", "BuiltInModuleAll"],
"BuiltInModuleSwitchElement"],
},
{
name: "BuiltInModuleSwitchElement",
implied_by: ["BuiltInModuleAll"],
}, },
{ {
name: "CacheInlineScriptCode" name: "CacheInlineScriptCode"
......
...@@ -5805,11 +5805,6 @@ crbug.com/963141 [ Mac ] virtual/audio-service/media/video-aspect-ratio.html [ P ...@@ -5805,11 +5805,6 @@ crbug.com/963141 [ Mac ] virtual/audio-service/media/video-aspect-ratio.html [ P
crbug.com/963141 [ Linux ] media/video-object-fit.html [ Pass Failure ] crbug.com/963141 [ Linux ] media/video-object-fit.html [ Pass Failure ]
crbug.com/963141 [ Linux ] virtual/audio-service/media/video-object-fit.html [ Pass Failure ] crbug.com/963141 [ Linux ] virtual/audio-service/media/video-object-fit.html [ Pass Failure ]
# Allow failure until its appearance gets stable.
crbug.com/972476 std-switch/switch-appearance.html [ Failure ]
crbug.com/972476 std-switch/switch-appearance-customization.html [ Failure ]
crbug.com/972476 std-switch/switch-appearance-theme.html [ Failure ]
# Sheriff 2019-05-20 # Sheriff 2019-05-20
crbug.com/965389 [ Mac ] media/track/track-cue-rendering-position-auto.html [ Pass Failure ] crbug.com/965389 [ Mac ] media/track/track-cue-rendering-position-auto.html [ Pass Failure ]
crbug.com/965389 [ Mac ] virtual/audio-service/media/track/track-cue-rendering-position-auto.html [ Pass Failure ] crbug.com/965389 [ Mac ] virtual/audio-service/media/track/track-cue-rendering-position-auto.html [ Pass Failure ]
...@@ -6704,8 +6699,6 @@ crbug.com/1050549 http/tests/devtools/unit/string-util.js [ Skip ] ...@@ -6704,8 +6699,6 @@ crbug.com/1050549 http/tests/devtools/unit/string-util.js [ Skip ]
# Sheriff 2020-02-21 # Sheriff 2020-02-21
crbug.com/1054916 [ Linux ] external/wpt/webxr/dom-overlay/ar_dom_overlay.https.html [ Pass Failure Timeout ] crbug.com/1054916 [ Linux ] external/wpt/webxr/dom-overlay/ar_dom_overlay.https.html [ Pass Failure Timeout ]
crbug.com/1055006 [ Linux ] external/wpt/std-toast/methods.html [ Pass Failure Timeout ]
crbug.com/1055006 [ Linux ] external/wpt/std-toast/options.html [ Pass Failure Timeout ]
crbug.com/1048597 [ Linux Debug ] virtual/android/fullscreen/video-scrolled-iframe.html [ Pass Timeout ] crbug.com/1048597 [ Linux Debug ] virtual/android/fullscreen/video-scrolled-iframe.html [ Pass Timeout ]
......
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script type="module">
import 'std:elements/switch';
test(t => {
assert_own_property(window, 'accessibilityController');
let switchElement = document.createElement('std-switch');
switchElement.id = 'switch';
switchElement.textContent = 'child text';
document.body.appendChild(switchElement);
let axSwitch = accessibilityController.accessibleElementById('switch');
assert_equals(axSwitch.role, 'AXRole: AXSwitch');
assert_equals(axSwitch.name, '', 'has no "child text"');
assert_equals(axSwitch.checked, 'false');
switchElement.on = true;
// Need to get AccessibleNode again
axSwitch = accessibilityController.accessibleElementById('switch');
assert_equals(axSwitch.checked, 'true');
}, 'Check accessibility behavior');
test(t => {
let switchElement = document.createElement('std-switch');
document.body.appendChild(switchElement);
switchElement.on = true;
assert_false(switchElement.hasAttribute('role'), 'role');
assert_false(switchElement.hasAttribute('aria-checked'), 'aria-checked');
}, 'Should have no a11y attributes');
test(t => {
assert_own_property(window, 'accessibilityController');
let switchElement = document.createElement('std-switch');
switchElement.id = 'switch3';
switchElement.setAttribute('role', 'checkbox');
switchElement.setAttribute('aria-checked', 'true');
document.body.appendChild(switchElement);
let axSwitch = accessibilityController.accessibleElementById('switch3');
assert_equals(axSwitch.role, 'AXRole: AXCheckBox');
assert_equals(axSwitch.checked, 'true');
}, 'Adding role=/aria-checked= should override the default');
</script>
</body>
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../resources/testdriver.js"></script>
<script src="../resources/testdriver-vendor.js"></script>
<body>
<p>Some text</p>
<script type="module">
import 'std:elements/switch';
function singleFrame() {
return new Promise((resolve, reject) => {
requestAnimationFrame(resolve);
});
}
function setupSwitchElement() {
let element = document.createElement('std-switch');
document.body.appendChild(element);
let thumb = internals.shadowRoot(element).getElementById('thumb');
element.transitionStartCounter = 0;
element.transitionEndCounter = 0;
thumb.addEventListener('transitionstart', e => {
++element.transitionStartCounter;
});
thumb.addEventListener('transitionend', e => {
++element.transitionEndCounter;
});
return element;
}
async function assertClickTransition(element) {
assert_false(element.on);
await test_driver.click(element);
assert_true(element.on, 'click');
await singleFrame();
assert_true(element.transitionStartCounter > 0, 'A transition starts');
if (element.transitionStartCounter > element.transitionEndCounter) {
await new Promise((resolve, reject) => {
let thumb = internals.shadowRoot(element).getElementById('thumb');
thumb.addEventListener('transitionend', () => {
if (element.transitionStartCounter == element.transitionEndCounter) {
resolve();
}
});
});
}
}
// This should be called just after an operation which should not start
// transition. element.transitionStartCounter must be 0 before the operation.
async function assertNoTransition(element) {
await singleFrame();
assert_equals(element.transitionStartCounter, 0, 'No transitionstart events');
}
promise_test(async () => {
let switchElement = setupSwitchElement();
await assertClickTransition(switchElement);
// Changing the state with an IDL attribute should not animate even after
// click transition.
switchElement.transitionStartCounter = 0;
switchElement.on = false;
await assertNoTransition(switchElement);
}, 'Click event handler should start transition');
promise_test(async () => {
let switchElement = setupSwitchElement();
switchElement.on = !switchElement.on;
await assertNoTransition(switchElement);
}, 'Changing the state with an IDL attribute should not start transition');
promise_test(async () => {
// crbug.com/992454 Transition didn't start if there is a text selection.
const range = document.createRange();
range.selectNodeContents(document.querySelector('p'));
window.getSelection().addRange(range);
await assertClickTransition(setupSwitchElement());
}, 'Click event handler should start transition even with text selection');
</script>
</body>
function generateSwitchElements(callback) {
const MASK_ON = 1;
const MASK_FOCUS = 1 << 1;
const MASK_HOVER = 1 << 2;
const MASK_ACTIVE = 1 << 3;
const MASK_DISABLED = 1 << 4;
const MASK_END = 1 << 5;
for (let mask = 0; mask < MASK_END; ++mask) {
let indicator = '';
let switchElement = document.createElement('std-switch');
if (mask & MASK_ON) {
switchElement.on = true;
indicator += 'o';
} else {
indicator += '-';
}
if (mask & MASK_FOCUS) {
internals.setPseudoClassState(switchElement, ':focus', true);
indicator += 'f';
} else {
indicator += '-';
}
if (mask & MASK_HOVER) {
internals.setPseudoClassState(switchElement, ':hover', true);
indicator += 'h';
} else {
indicator += '-';
}
if (mask & MASK_ACTIVE) {
internals.setPseudoClassState(switchElement, ':active', true);
indicator += 'a';
} else {
indicator += '-';
}
if (mask & MASK_DISABLED) {
switchElement.disabled = true;
indicator += 'd';
} else {
indicator += '-';
}
// Skip some impossible combinations
if (mask & MASK_DISABLED && (mask & MASK_FOCUS || mask & MASK_ACTIVE)) {
continue;
}
callback(indicator, switchElement);
}
}
<!DOCTYPE html>
<body>
<style>
std-switch {
margin: 0.5em;
}
/* ---------------------------------------------------------------- */
.material std-switch {
block-size: 20px;
inline-size: 36px;
--thumb-size: 20px;
--ripple-color: rgba(100,100,100,0.3);
--ripple-max-size: 48px;
}
.material std-switch::part(track) {
background: rgba(0,0,0,0.4);
block-size: 14px;
border: none;
box-shadow: none;
}
.material std-switch::part(track-fill) {
background: rgba(63,81,181,0.5);
}
.material std-switch::part(thumb) {
block-size: var(--thumb-size);
border-radius: calc(var(--thumb-size) / 2);
border: none;
box-shadow: 0 1px 5px 0 rgba(0,0,0,0.6);
inline-size: var(--thumb-size);
margin-inline-start: -100%;
}
.material std-switch[on]::part(thumb) {
background: rgb(63,81,181);
margin-inline-start: calc(0px - 20px);
}
.material std-switch:hover::part(thumb) {
inline-size: var(--thumb-size);
}
/*
* Ripple effect
*
* Translucent circle is painted on the thumb if the element is :active or
* :focus-visible. It has
* - Size transition when it appears
* - Opacity transition when it disappears
* part(thumb)::before represents the former, and part(thumb)::after represents
* the latter.
*/
.material std-switch::part(thumb)::before {
background: var(--ripple-color);
block-size: 0px;
border-radius: 0px;
content: "";
display: inline-block;
inline-size: 0px;
left: calc(var(--thumb-size) / 2);
position: relative;
top: calc(var(--thumb-size) / 2);
transition: none;
}
.material std-switch:active::part(thumb)::before,
.material std-switch:focus-visible::part(thumb)::before {
block-size: var(--ripple-max-size);
border-radius: calc(var(--ripple-max-size) / 2);
inline-size: var(--ripple-max-size);
left: calc((var(--thumb-size) - var(--ripple-max-size)) / 2);
top: calc((var(--thumb-size) - var(--ripple-max-size)) / 2);
/* transition: all linear 0.1s; transition is harmful in pixel tests. */
}
.material std-switch::part(thumb)::after {
background: var(--ripple-color);
block-size: var(--ripple-max-size);
border-radius: calc(var(--ripple-max-size) / 2);
content: "";
display: inline-block;
inline-size: var(--ripple-max-size);
left: calc((var(--thumb-size) - var(--ripple-max-size)) / 2);
opacity: 0;
position: relative;
/* Why 18px? */
top: calc((var(--thumb-size) - var(--ripple-max-size)) / 2 - 18px);
/* transition: opacity linear 0.3s; transition is harmful in pixel tests. */
}
.material std-switch:active::part(thumb)::after,
.material std-switch:focus-visible::part(thumb)::after {
block-size: 0px;
content: "";
inline-size: 0px;
opacity: 1;
transition: none;
}
/* ---------------------------------------------------------------- */
.cocoa std-switch {
block-size: 31px;
inline-size: 51px;
}
.cocoa std-switch::part(track) {
border-radius: 15px;
border: 1px solid lightgray;
box-shadow: none;
}
.cocoa std-switch::part(track-fill) {
background: #4ad963;
}
.cocoa std-switch[on]::part(track) {
border: 1px solid #4ad963;
}
.cocoa std-switch::part(thumb) {
block-size: 29px;
border-radius: 14.5px;
border: none;
box-shadow: 0px 3px 4px 1px rgba(0,0,0,0.2);
inline-size: 29px;
margin-inline-start: calc(-100% + 1px);
}
.cocoa std-switch[on]::part(thumb) {
margin-inline-start: calc(0px - 29px - 1px);
}
/* ---------------------------------------------------------------- */
.fluent std-switch {
block-size: 20px;
inline-size: 44px;
}
.fluent std-switch::part(track) {
border: 2px solid #333333;
box-shadow: none;
}
.fluent std-switch[on]::part(track) {
border: 2px solid #4cafff;
}
.fluent std-switch::part(track-fill) {
background: #4cafff;
}
.fluent std-switch::part(thumb) {
background: #333333;
block-size: 10px;
border-radius: 5px;
border: none;
inline-size: 10px;
margin-inline-start: calc(-100% + 5px);
}
.fluent std-switch[on]::part(thumb) {
background: white;
margin-inline-start: calc(0px - 10px - 5px);
}
.fluent std-switch:active::part(track) {
background: darkgray;
border: 2px solid darkgray;
}
.fluent std-switch:active::part(thumb) {
background: white;
}
/* ---------------------------------------------------------------- */
div.material, div.cocoa, div.fluent {
overflow: auto;
}
.container {
float: left;
font-family: monospace;
}
</style>
<div class="material"><h2>Material-like</h2></div>
<div class="cocoa"><h2>Cocoa-like</h2></div>
<div class="fluent"><h2>Fluent-like</h2></div>
<script src="resources/state-combo.js"></script>
<script type="module">
import 'std:elements/switch';
function wrapAndConnect(query, indicator, element) {
let container = document.createElement('div');
container.className = 'container';
container.appendChild(document.createTextNode(indicator));
container.appendChild(element);
document.querySelector(query).appendChild(container);
}
generateSwitchElements(wrapAndConnect.bind(null, 'div.material'));
generateSwitchElements(wrapAndConnect.bind(null, 'div.cocoa'));
generateSwitchElements(wrapAndConnect.bind(null, 'div.fluent'));
</script>
</body>
<!DOCTYPE html>
<body>
<script type="module">
import 'std:elements/switch';
document.body.insertAdjacentHTML('beforeend', `
<h2>Attribute on the element</h2>
<std-switch theme="platform-agnostic"></std-switch>
<std-switch theme="match-platform"></std-switch>`);
document.body.insertAdjacentHTML('beforeend', `
<h2>Attribute on an ancestor</h2>
<span theme="platform-agnostic"><std-switch></std-switch></span>
<i theme="match-platform"><b><std-switch></std-switch></b></i>`);
document.body.insertAdjacentHTML('beforeend', `
<h2>Nested attributes</h2>
<s theme="match-platform"><span theme="platform-agnostic"><code theme="foo"
><std-switch></std-switch></code></span></s>
<kbd theme="platform-agnostic"><i theme="match-platform"><b><std-switch></std-switch></b></i></kbd>`);
document.body.insertAdjacentHTML('beforeend', `
<h2>Attribute over shadow-boundary</h2>
<span theme="platform-agnostic"><span id="host1"></span></span>
<i theme="match-platform"><b><span id="host2"></span></b></i>`);
let root1 = document.querySelector('#host1').attachShadow({mode:'open'});
root1.innerHTML = '<std-switch></std-switch>';
let root2 = document.querySelector('#host2').attachShadow({mode:'closed'});
root2.innerHTML = '<std-switch></std-switch>';
document.body.insertAdjacentHTML('beforeend', `
<h2>NO support for updating attribute values for now</h2>
<std-switch id="update1" theme="match-platform"></std-switch>
<std-switch id="update2" theme="platform-agnostic"></std-switch>`);
document.querySelector('#update1').setAttribute('theme', 'platform-agnostic');
document.querySelector('#update2').setAttribute('theme', 'match-platform');
</script>
</body>
<!DOCTYPE html>
<body>
<style>
std-switch {
/* TODO(tkent): Should have margin in UA style? */
margin: 0.5em;
}
.container {
float: left;
font-family: monospace;
}
</style>
<script type="module">
import 'std:elements/switch';
</script>
<div><std-switch><div style="display:inline-block; text-align:right; width:100%;">off</div></std-switch></div>
<div><std-switch on>on</std-switch></div>
<div>Hidden attribute: <std-switch hidden></std-switch> <std-switch on hidden></std-switch></div>
<script src="resources/state-combo.js"></script>
<script type="module">
generateSwitchElements((indicator, element) => {
let container = document.createElement('div');
container.className = 'container';
container.appendChild(document.createTextNode(indicator));
container.appendChild(element);
document.body.appendChild(container);
});
</script>
</body>
# Tests for std-switch
https://github.com/tkent-google/std-switch/blob/master/README.md
Tests in tentative/ should be upstreamed to WPT in the future.
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<body>
<script type="module">
import 'std:elements/switch';
promise_test(async t => {
let switchElement = document.createElement('std-switch');
document.body.appendChild(switchElement);
assert_false(switchElement.on);
let inputEventCounter = 0;
let changeEventCounter = 0;
switchElement.oninput = t.step_func(e => {
++inputEventCounter;
assert_true(e.bubbles);
assert_false(e.cancelable);
assert_false(e.composed);
assert_false(e.isTrusted);
});
switchElement.onchange = t.step_func(e => {
++changeEventCounter;
assert_true(e.bubbles);
assert_false(e.cancelable);
assert_false(e.composed);
assert_false(e.isTrusted);
});
await test_driver.click(switchElement);
assert_true(switchElement.on);
assert_equals(inputEventCounter, 1);
assert_equals(changeEventCounter, 1);
await test_driver.click(switchElement);
assert_false(switchElement.on);
assert_equals(inputEventCounter, 2);
assert_equals(changeEventCounter, 2);
switchElement.disabled = true;
await test_driver.click(switchElement);
// No changes
assert_false(switchElement.on);
assert_equals(inputEventCounter, 2);
assert_equals(changeEventCounter, 2);
}, 'Click should change the status, and dispatch input/change events.');
test(() => {
let switchElement = document.createElement('std-switch');
let inputEventCounter = 0;
let changeEventCounter = 0;
switchElement.oninput = e => {
++inputEventCounter;
};
switchElement.onchange = e => {
++changeEventCounter;
};
switchElement.click();
assert_equals(inputEventCounter, 1);
assert_equals(changeEventCounter, 1);
}, 'input/change events should be synchronous.');
promise_test(async t => {
let switchElement = document.createElement('std-switch');
document.body.appendChild(switchElement);
assert_false(switchElement.on);
let inputEventCounter = 0;
let changeEventCounter = 0;
switchElement.oninput = t.step_func(e => {
++inputEventCounter;
assert_true(e.bubbles);
assert_false(e.cancelable);
assert_false(e.composed);
assert_false(e.isTrusted);
});
switchElement.onchange = t.step_func(e => {
++changeEventCounter;
assert_true(e.bubbles);
assert_false(e.cancelable);
assert_false(e.composed);
assert_false(e.isTrusted);
});
await test_driver.send_keys(switchElement, ' ');
assert_true(switchElement.on, 'on');
assert_equals(inputEventCounter, 1);
assert_equals(changeEventCounter, 1);
await test_driver.send_keys(switchElement, ' ');
assert_false(switchElement.on, 'off');
assert_equals(inputEventCounter, 2);
assert_equals(changeEventCounter, 2);
}, 'Space key is equivalent to click.');
</script>
</body>
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script type="module">
import { StdSwitchElement } from 'std:elements/switch';
setup({allow_uncaught_exception:true});
test(()=> {
class MySwitchElement extends StdSwitchElement {
}
customElements.define('my-switch', MySwitchElement);
let uncaughtError = null;
window.addEventListener('error', e => {
uncaughtError = e.error;
}, {once: true});
document.createElement('my-switch');
assert_true(uncaughtError instanceof TypeError);
}, 'Should not be able to create an autonomous custom element extending StdSwitchElement');
test(()=> {
class MySwitchElement2 extends StdSwitchElement {
}
assert_throws_dom('NotSupportedError', () => {
customElements.define('my-switch2', MySwitchElement2, {extends: 'std-switch'});
});
}, 'Should not be able to define a customized built-in element for std-switch');
</script>
</body>
This is a testharness.js-based test.
PASS should be focus()-able by default
FAIL should have no tabindex by default assert_false: expected false got true
PASS should not be focusable with invalid tabindex
PASS should not be focusable if disabled
Harness: the test ran to completion.
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script type="module">
import 'std:elements/switch';
test(() => {
let switchElement = document.createElement('std-switch');
document.body.appendChild(switchElement);
switchElement.focus();
assert_equals(document.activeElement, switchElement);
assert_true(switchElement.matches(':focus'));
}, 'should be focus()-able by default');
test(() => {
let switchElement = document.createElement('std-switch');
document.body.appendChild(switchElement);
assert_false(switchElement.hasAttribute('tabindex'));
}, 'should have no tabindex by default');
test(() => {
let switchElement = document.createElement('std-switch');
switchElement.setAttribute('tabindex', 'parse-error');
document.body.appendChild(switchElement);
switchElement.focus();
assert_not_equals(document.activeElement, switchElement);
}, 'should not be focusable with invalid tabindex');
test(() => {
let switchElement = document.createElement('std-switch');
switchElement.disabled = true;
document.body.appendChild(switchElement);
switchElement.focus();
assert_not_equals(document.activeElement, switchElement);
}, 'should not be focusable if disabled');
</script>
</body>
const HTML_DOM = '/html/dom/';
function loadScript(src) {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
script.src = src;
script.addEventListener('load', e => resolve(), {once: true});
script.addEventListener('error', e => reject(), {once: true});
document.body.appendChild(script);
});
}
function formAssociatedTests(tag, reflectionDict) {
test(() => {
// TODO(tkent): Move the following checks to idlharness.js?
let interface = document.createElement(tag).constructor;
assert_not_own_property(interface, 'disabledFeatures');
assert_not_own_property(interface, 'formAssociated');
assert_not_own_property(interface, 'observedAttributes');
assert_not_own_property(interface.prototype, 'adoptedCallback');
assert_not_own_property(interface.prototype, 'attributeChangedCallback');
assert_not_own_property(interface.prototype, 'connectedCallback');
assert_not_own_property(interface.prototype, 'disconnectedCallback');
assert_not_own_property(interface.prototype, 'formAssociatedCallback');
assert_not_own_property(interface.prototype, 'formDisabledCallback');
assert_not_own_property(interface.prototype, 'formResetCallback');
assert_not_own_property(interface.prototype, 'formStateRestoreCallback');
}, `Interface for ${tag} should not have custom-element-like properties`);
test(() => {
let control = document.createElement(tag);
assert_array_equals(Object.getOwnPropertySymbols(control), []);
}, `A ${tag} instance should not have Symbol properties`);
test(() => {
let control = document.createElement(tag);
assert_equals(control.type, tag);
}, `${tag} supports type property`);
test(() => {
let control = document.createElement(tag);
assert_equals(control.form, null);
let form1 = document.createElement('form');
form1.appendChild(control);
assert_equals(control.form, form1);
let form2 = document.createElement('form');
form2.id = 'connected-form';
document.body.appendChild(form2);
control.setAttribute('form', 'connected-form');
control.remove();
assert_equals(control.form, null);
document.body.appendChild(control);
assert_equals(control.form, document.getElementById('connected-form'));
}, `${tag} supports form property`);
test(() => {
let control = document.createElement(tag);
assert_true(control.willValidate);
control.setAttribute('disabled', '');
assert_false(control.willValidate);
control.removeAttribute('disabled');
let datalist = document.createElement('datalist');
datalist.appendChild(control);
assert_false(control.willValidate);
}, `${tag} supports willValidate property`);
test(() => {
let control = document.createElement(tag);
assert_true(control.validity.valid);
assert_false(control.validity.customError);
assert_equals(control.validationMessage, '');
assert_true(control.checkValidity(), '1: ' + control);
assert_true(control.reportValidity());
control.setCustomValidity('Invalid!');
assert_false(control.validity.valid);
assert_true(control.validity.customError);
assert_equals(control.validationMessage, 'Invalid!');
assert_false(control.checkValidity(), '2: ' + control);
assert_false(control.reportValidity());
}, `${tag} supports form validation`);
test(() => {
let control = document.createElement(tag);
assert_equals(control.labels.length, 0);
let label = document.createElement('label');
document.body.appendChild(label);
label.appendChild(control);
assert_array_equals(control.labels, [label], 'descendant association');
assert_equals(label.control, control);
document.body.appendChild(control);
document.body.appendChild(label);
assert_equals(control.labels.length, 0);
control.id = 'control';
label.htmlFor = 'control';
assert_array_equals(control.labels, [label], 'for= association');
assert_equals(label.control, control);
}, `${tag} supports labels property`);
promise_test(async () => {
await loadScript(HTML_DOM + 'original-harness.js');
await loadScript(HTML_DOM + 'new-harness.js');
let targetElements = {};
targetElements[tag] = {
disabled: 'boolean',
name: 'string',
};
for (let [key, value] of Object.entries(reflectionDict)) {
targetElements[tag][key] = value;
}
mergeElements(targetElements);
await loadScript(HTML_DOM + 'reflection.js');
}, 'Setup reflection tests');
}
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<div id="container"></div>
<script type=module>
import 'std:elements/switch';
const $ = document.querySelector.bind(document);
function resetAndAssertOnIs(expected) {
$('form').reset();
assert_equals($('std-switch').on, expected);
}
test(() => {
$('#container').innerHTML = `
<form><std-switch></std-switch></form>`;
$('std-switch').on = true;
resetAndAssertOnIs(false);
}, 'no "on" and no "defaulton"');
test(() => {
$('#container').innerHTML = `
<form><std-switch defaulton></std-switch></form>`;
resetAndAssertOnIs(true);
}, 'no "on" and "defaulton"');
test(() => {
$('#container').innerHTML = `
<form><std-switch on></std-switch></form>`;
resetAndAssertOnIs(false);
}, '"on" and no "defaulton"');
test(() => {
$('#container').innerHTML = `
<form><std-switch on defaulton></std-switch></form>`;
$('std-switch').on = false;
resetAndAssertOnIs(true);
}, '"on" and "defaulton"');
</script>
</body>
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/html/semantics/forms/form-submission-0/resources/targetted-form.js"></script>
<body>
<div id="container"></div>
<script type="module">
import 'std:elements/switch';
promise_test(async () => {
const form = populateForm('<std-switch on></std-switch>');
const query = await submitPromise(form, form.previousSibling);
assert_equals(query.indexOf('on'), -1);
}, 'No "name" attribute');
promise_test(async () => {
const form = populateForm('<std-switch name=switch1></std-switch>' +
'<std-switch name=switch2 on></std-switch>');
const query = await submitPromise(form, form.previousSibling);
assert_not_equals(query.indexOf('switch1=off'), -1,
`${query} should contain "switch1=off".`);
assert_not_equals(query.indexOf('switch2=on'), -1,
`${query} should contain "switch2=on".`);
}, 'Normal case');
</script>
</body>
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
test(() => {
assert_not_own_property(window, 'StdSwitchElement');
assert_true(document.createElement('std-switch') instanceof HTMLElement);
}, 'StdSwitchElement should not be available before import');
let t = async_test('StdSwitchElement should be available after import');
</script>
<script type="module">
import { StdSwitchElement } from 'std:elements/switch';
t.step(() => {
assert_not_own_property(window, 'StdSwitchElement');
assert_true(document.createElement('std-switch') instanceof StdSwitchElement);
t.done();
});
</script>
</body>
This is a testharness.js-based test.
FAIL interface promise_test: Unhandled rejection with value: object "StdSwitchElement inherits HTMLElement, but HTMLElement is undefined."
Harness: the test ran to completion.
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/webidl2/lib/webidl2.js"></script>
<script src="/resources/idlharness.js"></script>
<script type="module">
import { StdSwitchElement } from 'std:elements/switch';
const DEPS = ['html', 'SVG', 'cssom', 'touch-events', 'uievents', 'dom', 'xhr'];
// WebIDL and idlharness.js are not capable of interfaces in a module.
const SWITCH_IDL = `
[Exposed=Window,
Constructor()]
interface StdSwitchElement : HTMLElement {
[CEReactions] attribute boolean defaultOn;
[CEReactions] attribute boolean on;
[CEReactions] attribute boolean disabled;
readonly attribute HTMLFormElement? form;
[CEReactions] attribute DOMString name;
readonly attribute DOMString type;
readonly attribute boolean willValidate;
readonly attribute ValidityState validity;
readonly attribute DOMString validationMessage;
boolean checkValidity();
boolean reportValidity();
void setCustomValidity(DOMString error);
readonly attribute NodeList labels;
};`;
Object.defineProperty(window, 'StdSwitchElement', {
configurable: true,
enumerable: false,
value: StdSwitchElement,
writable: true,
});
promise_test(async () => {
let idlArray = new IdlArray();
let dependentIdls = await Promise.all(DEPS.map(spec => { return fetch_spec(spec); }));
idlArray.add_idls(SWITCH_IDL);
for (let dep of dependentIdls) {
idlArray.add_dependency_idls(dep);
}
idlArray.add_objects({StdSwitchElement: ['document.createElement("std-switch")']});
idlArray.test();
});
</script>
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script src="form-associated-basic.js"></script>
<script type=module>
import 'std:elements/switch';
formAssociatedTests('std-switch', {
defaultOn: {type: 'boolean', domAttrName: 'defaulton'},
on: 'boolean'
});
</script>
</body>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment