Skip to content

Commit bb310a3

Browse files
committed
Fix checks for EdgeHTML engine, because it mocks Chromes Blink engine.
1 parent 29131bb commit bb310a3

File tree

3 files changed

+108
-91
lines changed

3 files changed

+108
-91
lines changed

src/demo-app/platform/platform-demo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {MdPlatform} from '@angular/material';
99
<p>Is Blink: {{ platform.BLINK }}</p>
1010
<p>Is Webkit: {{ platform.WEBKIT }}</p>
1111
<p>Is Trident: {{ platform.TRIDENT }}</p>
12+
<p>Is Edge: {{ platform.EDGE }}</p>
1213
1314
`
1415
})

src/lib/core/a11y/interactivity-checker.spec.ts

Lines changed: 95 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -237,134 +237,142 @@ describe('InteractivityChecker', () => {
237237
});
238238
});
239239

240-
it('should return true for div and span with tabindex == 0', () => {
241-
let elements = createElements('div', 'span');
242-
243-
elements.forEach(el => el.setAttribute('tabindex', '0'));
244-
appendElements(elements);
245240

246-
elements.forEach(el => {
247-
expect(checker.isFocusable(el))
248-
.toBe(true, `Expected <${el.nodeName} tabindex="0"> to be focusable`);
249-
});
250-
});
251241
});
252242

253243
describe('isTabbable', () => {
254-
it('should return true for native form controls and anchor without tabindex attribute', () => {
255-
let elements = createElements('input', 'textarea', 'select', 'button', 'a');
256-
appendElements(elements);
257244

258-
elements.forEach(el => {
259-
expect(checker.isTabbable(el)).toBe(true, `Expected <${el.nodeName}> to be tabbable`);
260-
});
261-
});
262245

263-
it('should return false for native form controls and anchor with tabindex == -1', () => {
264-
let elements = createElements('input', 'textarea', 'select', 'button', 'a');
265246

266-
elements.forEach(el => el.setAttribute('tabindex', '-1'));
267-
appendElements(elements);
247+
it('should respect the tabindex for video elements with controls',
248+
// Do not run for Blink and Firefox, because those treat video elements
249+
// with controls different.
250+
runIf(!platform.BLINK && !platform.FIREFOX, () => {
268251

269-
elements.forEach(el => {
270-
expect(checker.isTabbable(el))
271-
.toBe(false, `Expected <${el.nodeName} tabindex="-1"> not to be tabbable`);
272-
});
273-
});
252+
let video = createFromTemplate('<video controls>', true);
274253

275-
it('should return true for div and span with tabindex == 0', () => {
276-
let elements = createElements('div', 'span');
254+
expect(checker.isTabbable(video)).toBe(true);
277255

278-
elements.forEach(el => el.setAttribute('tabindex', '0'));
279-
appendElements(elements);
256+
video.tabIndex = -1;
280257

281-
elements.forEach(el => {
282-
expect(checker.isTabbable(el))
283-
.toBe(true, `Expected <${el.nodeName} tabindex="0"> to be tabbable`);
284-
});
285-
});
258+
expect(checker.isTabbable(video)).toBe(false);
259+
})
260+
);
286261

287-
it('should respect the inherited tabindex inside of frame elements', () => {
288-
let iframe = createFromTemplate('<iframe>', true) as HTMLFrameElement;
289-
let button = createFromTemplate('<button tabindex="0">Not Tabbable</button>');
262+
it('should always mark video elements with controls as tabbable (BLINK & FIREFOX)',
263+
// Only run this spec for Blink and Firefox, because those always treat video
264+
// elements with controls as tabbable.
265+
runIf(platform.BLINK || platform.FIREFOX, () => {
290266

291-
appendElements([iframe]);
267+
let video = createFromTemplate('<video controls>', true);
292268

293-
iframe.tabIndex = -1;
294-
iframe.contentDocument.body.appendChild(button);
269+
expect(checker.isTabbable(video)).toBe(true);
295270

296-
expect(checker.isTabbable(iframe)).toBe(false);
297-
expect(checker.isTabbable(button)).toBe(false);
271+
video.tabIndex = -1;
298272

299-
iframe.tabIndex = null;
273+
expect(checker.isTabbable(video)).toBe(true);
274+
})
275+
);
300276

301-
expect(checker.isTabbable(iframe)).toBe(false);
302-
expect(checker.isTabbable(button)).toBe(true);
303-
});
277+
// Some tests should not run inside of iOS browsers, because those only allow specific
278+
// elements to be tabbable and cause the tests to always fail.
279+
describe('for non-iOS browsers', runIf(!platform.IOS, () => {
304280

281+
it('should mark form controls and anchors without tabindex attribute as tabbable', () => {
282+
let elements = createElements('input', 'textarea', 'select', 'button', 'a');
283+
appendElements(elements);
305284

306-
it('should mark elements which are contentEditable as tabbable', async(() => {
307-
let editableEl = createFromTemplate('<div contenteditable="true">', true);
285+
elements.forEach(el => {
286+
expect(checker.isTabbable(el)).toBe(true, `Expected <${el.nodeName}> to be tabbable`);
287+
});
288+
});
308289

309-
// Wait one tick, because the browser takes some time to update the tabIndex
310-
// according to the contentEditable attribute.
311-
setTimeout(() => {
290+
it('should return true for div and span with tabindex == 0', () => {
291+
let elements = createElements('div', 'span');
312292

313-
expect(checker.isTabbable(editableEl)).toBe(true);
293+
elements.forEach(el => el.setAttribute('tabindex', '0'));
294+
appendElements(elements);
314295

315-
editableEl.tabIndex = -1;
296+
elements.forEach(el => {
297+
expect(checker.isFocusable(el))
298+
.toBe(true, `Expected <${el.nodeName} tabindex="0"> to be focusable`);
299+
});
300+
});
316301

317-
expect(checker.isTabbable(editableEl)).toBe(false);
302+
it('should return false for native form controls and anchor with tabindex == -1', () => {
303+
let elements = createElements('input', 'textarea', 'select', 'button', 'a');
318304

319-
}, 1);
305+
elements.forEach(el => el.setAttribute('tabindex', '-1'));
306+
appendElements(elements);
320307

321-
}));
308+
elements.forEach(el => {
309+
expect(checker.isTabbable(el))
310+
.toBe(false, `Expected <${el.nodeName} tabindex="-1"> not to be tabbable`);
311+
});
312+
});
322313

323-
it('should never mark iframe elements as tabbable', () => {
324-
let iframe = createFromTemplate('<iframe>', true);
314+
it('should return true for div and span with tabindex == 0', () => {
315+
let elements = createElements('div', 'span');
325316

326-
// iFrame elements will be never marked as tabbable, because it depends on the content
327-
// which is mostly not detectable due to CORS and also the checks will be not reliable.
328-
expect(checker.isTabbable(iframe)).toBe(false);
329-
});
317+
elements.forEach(el => el.setAttribute('tabindex', '0'));
318+
appendElements(elements);
330319

320+
elements.forEach(el => {
321+
expect(checker.isTabbable(el))
322+
.toBe(true, `Expected <${el.nodeName} tabindex="0"> to be tabbable`);
323+
});
324+
});
331325

326+
it('should respect the inherited tabindex inside of frame elements', () => {
327+
let iframe = createFromTemplate('<iframe>', true) as HTMLFrameElement;
328+
let button = createFromTemplate('<button tabindex="0">Not Tabbable</button>');
332329

333-
it('should always mark audio elements without controls as not tabbable', () => {
334-
let audio = createFromTemplate('<audio>', true);
330+
appendElements([iframe]);
335331

336-
expect(checker.isTabbable(audio)).toBe(false);
337-
});
332+
iframe.tabIndex = -1;
333+
iframe.contentDocument.body.appendChild(button);
338334

339-
it('should respect the tabindex for video elements with controls',
340-
// Do not run for Blink and Firefox, because those treat video elements
341-
// with controls different.
342-
runIf(!platform.BLINK && !platform.FIREFOX, () => {
335+
expect(checker.isTabbable(iframe)).toBe(false);
336+
expect(checker.isTabbable(button)).toBe(false);
343337

344-
let video = createFromTemplate('<video controls>', true);
338+
iframe.tabIndex = null;
345339

346-
expect(checker.isTabbable(video)).toBe(true);
340+
expect(checker.isTabbable(iframe)).toBe(false);
341+
expect(checker.isTabbable(button)).toBe(true);
342+
});
347343

348-
video.tabIndex = -1;
344+
it('should mark elements which are contentEditable as tabbable', async(() => {
345+
let editableEl = createFromTemplate('<div contenteditable="true">', true);
349346

350-
expect(checker.isTabbable(video)).toBe(false);
351-
})
352-
);
347+
// Wait one tick, because the browser takes some time to update the tabIndex
348+
// according to the contentEditable attribute.
349+
setTimeout(() => {
353350

354-
it('should always mark video elements with controls as tabbable (BLINK & FIREFOX)',
355-
// Only run this spec for Blink and Firefox, because those always treat video
356-
// elements with controls as tabbable.
357-
runIf(platform.BLINK || platform.FIREFOX, () => {
351+
expect(checker.isTabbable(editableEl)).toBe(true);
358352

359-
let video = createFromTemplate('<video controls>', true);
353+
editableEl.tabIndex = -1;
360354

361-
expect(checker.isTabbable(video)).toBe(true);
355+
expect(checker.isTabbable(editableEl)).toBe(false);
362356

363-
video.tabIndex = -1;
357+
}, 1);
364358

365-
expect(checker.isTabbable(video)).toBe(true);
366-
})
367-
);
359+
}));
360+
361+
it('should never mark iframe elements as tabbable', () => {
362+
let iframe = createFromTemplate('<iframe>', true);
363+
364+
// iFrame elements will be never marked as tabbable, because it depends on the content
365+
// which is mostly not detectable due to CORS and also the checks will be not reliable.
366+
expect(checker.isTabbable(iframe)).toBe(false);
367+
});
368+
369+
it('should always mark audio elements without controls as not tabbable', () => {
370+
let audio = createFromTemplate('<audio>', true);
371+
372+
expect(checker.isTabbable(audio)).toBe(false);
373+
});
374+
375+
}));
368376

369377
describe('for Blink and Webkit browsers', runIf(platform.BLINK || platform.WEBKIT, () => {
370378

src/lib/core/platform/platform.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@ import {Injectable, NgModule, ModuleWithProviders} from '@angular/core';
33
// Declare window with type of any.
44
declare const window: any;
55

6+
// Whether the current platform supports the V8 Break Iterator. The V8 check
7+
// is necessary to detect all Blink based browsers.
8+
const hasV8BreakIterator = (window.Intl && (window.Intl as any).v8BreakIterator);
9+
610
@Injectable()
711
export class MdPlatform {
812

9-
/* Layout Engines */
10-
BLINK = !!(window.chrome || (window.Intl && (window.Intl as any).v8BreakIterator)) && !!CSS;
11-
WEBKIT = /AppleWebKit/i.test(navigator.userAgent) && !this.BLINK;
13+
/** Layout Engines */
14+
EDGE = /(edge)/i.test(navigator.userAgent);
15+
BLINK = !!(window.chrome || hasV8BreakIterator) && !!CSS && !this.EDGE;
16+
17+
// Webkit is part of the userAgent in EdgeHTML and Blink, so we need to ensure that Webkit
18+
// runs standalone.
19+
WEBKIT = /AppleWebKit/i.test(navigator.userAgent) && !this.BLINK && !this.EDGE;
1220
TRIDENT = /(msie|trident)/i.test(navigator.userAgent);
1321

14-
/* Browsers and Platform Types */
22+
/** Browsers and Platform Types */
1523
ANDROID = /android/i.test(navigator.userAgent);
1624
IOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
1725
FIREFOX = /(firefox|minefield)/i.test(navigator.userAgent);

0 commit comments

Comments
 (0)