Initial safari compatibility (#1609)
* Update environment info to return the 'safari' browser * Fix popup display on Safari * Update environment assignment * Add data-loading-stalled property when loading takes longer than expected * Add notification when loading has stalled * Allow getDictionaryInfo invocation on non-privileged contexts * Update _validatePrivilegedMessageSender * Don't listen to 'voiceschanged' event unless addEventListener is present Also expose an event
This commit is contained in:
parent
b23c4bff4b
commit
20d60a2ba7
@ -134,7 +134,7 @@ ul+p,
|
|||||||
ul+ol,
|
ul+ol,
|
||||||
ul+ul,
|
ul+ul,
|
||||||
li {
|
li {
|
||||||
margin: 0.425em 0;
|
margin: 0.425em 0 0;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: var(--link-color);
|
color: var(--link-color);
|
||||||
@ -2152,6 +2152,13 @@ button.hotkey-list-item-enabled-button[data-scope-count='0'] {
|
|||||||
margin-left: 0.375em;
|
margin-left: 0.375em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-loading-stalled-notification {
|
||||||
|
border: 1px solid var(--danger-color);
|
||||||
|
}
|
||||||
|
:root:not([data-loading-stalled=true]) .page-loading-stalled-notification {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Generic layouts */
|
/* Generic layouts */
|
||||||
.margin-above {
|
.margin-above {
|
||||||
@ -2233,19 +2240,25 @@ button.hotkey-list-item-enabled-button[data-scope-count='0'] {
|
|||||||
|
|
||||||
|
|
||||||
/* Environment-specific display */
|
/* Environment-specific display */
|
||||||
|
:root[data-browser=unknown] [data-show-for-browser],
|
||||||
|
:root[data-browser=unknown] [data-hide-for-browser],
|
||||||
:root[data-browser=edge] [data-show-for-browser]:not([data-show-for-browser~=edge]),
|
:root[data-browser=edge] [data-show-for-browser]:not([data-show-for-browser~=edge]),
|
||||||
:root[data-browser=edge-legacy] [data-show-for-browser]:not([data-show-for-browser~=edge-legacy]),
|
:root[data-browser=edge-legacy] [data-show-for-browser]:not([data-show-for-browser~=edge-legacy]),
|
||||||
:root[data-browser=chrome] [data-show-for-browser]:not([data-show-for-browser~=chrome]),
|
:root[data-browser=chrome] [data-show-for-browser]:not([data-show-for-browser~=chrome]),
|
||||||
|
:root[data-browser=safari] [data-show-for-browser]:not([data-show-for-browser~=safari]),
|
||||||
:root[data-browser=firefox] [data-show-for-browser]:not([data-show-for-browser~=firefox]),
|
:root[data-browser=firefox] [data-show-for-browser]:not([data-show-for-browser~=firefox]),
|
||||||
:root[data-browser=firefox-mobile] [data-show-for-browser]:not([data-show-for-browser~=firefox-mobile]),
|
:root[data-browser=firefox-mobile] [data-show-for-browser]:not([data-show-for-browser~=firefox-mobile]),
|
||||||
:root[data-browser=edge] [data-hide-for-browser~=edge],
|
:root[data-browser=edge] [data-hide-for-browser~=edge],
|
||||||
:root[data-browser=edge-legacy] [data-hide-for-browser~=edge-legacy],
|
:root[data-browser=edge-legacy] [data-hide-for-browser~=edge-legacy],
|
||||||
:root[data-browser=chrome] [data-hide-for-browser~=chrome],
|
:root[data-browser=chrome] [data-hide-for-browser~=chrome],
|
||||||
|
:root[data-browser=safari] [data-hide-for-browser~=safari],
|
||||||
:root[data-browser=firefox] [data-hide-for-browser~=firefox],
|
:root[data-browser=firefox] [data-hide-for-browser~=firefox],
|
||||||
:root[data-browser=firefox-mobile] [data-hide-for-browser~=firefox-mobile] {
|
:root[data-browser=firefox-mobile] [data-hide-for-browser~=firefox-mobile] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root[data-os=unknown] [data-show-for-os],
|
||||||
|
:root[data-os=unknown] [data-hide-for-os],
|
||||||
:root[data-os=mac] [data-show-for-os]:not([data-show-for-os~=mac]),
|
:root[data-os=mac] [data-show-for-os]:not([data-show-for-os~=mac]),
|
||||||
:root[data-os=win] [data-show-for-os]:not([data-show-for-os~=win]),
|
:root[data-os=win] [data-show-for-os]:not([data-show-for-os~=win]),
|
||||||
:root[data-os=android] [data-show-for-os]:not([data-show-for-os~=android]),
|
:root[data-os=android] [data-show-for-os]:not([data-show-for-os~=android]),
|
||||||
@ -2261,6 +2274,8 @@ button.hotkey-list-item-enabled-button[data-scope-count='0'] {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root[data-manifest-version=unknown] [data-show-for-manifest-version],
|
||||||
|
:root[data-manifest-version=unknown] [data-hide-for-manifest-version],
|
||||||
:root[data-manifest-version='2'] [data-show-for-manifest-version]:not([data-show-for-manifest-version~='2']),
|
:root[data-manifest-version='2'] [data-show-for-manifest-version]:not([data-show-for-manifest-version~='2']),
|
||||||
:root[data-manifest-version='3'] [data-show-for-manifest-version]:not([data-show-for-manifest-version~='3']),
|
:root[data-manifest-version='3'] [data-show-for-manifest-version]:not([data-show-for-manifest-version~='3']),
|
||||||
:root[data-manifest-version='2'] [data-hide-for-manifest-version~='2'],
|
:root[data-manifest-version='2'] [data-hide-for-manifest-version~='2'],
|
||||||
|
@ -112,7 +112,7 @@ class Backend {
|
|||||||
['getDisplayTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetDisplayTemplatesHtml.bind(this)}],
|
['getDisplayTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetDisplayTemplatesHtml.bind(this)}],
|
||||||
['getZoom', {async: true, contentScript: true, handler: this._onApiGetZoom.bind(this)}],
|
['getZoom', {async: true, contentScript: true, handler: this._onApiGetZoom.bind(this)}],
|
||||||
['getDefaultAnkiFieldTemplates', {async: false, contentScript: true, handler: this._onApiGetDefaultAnkiFieldTemplates.bind(this)}],
|
['getDefaultAnkiFieldTemplates', {async: false, contentScript: true, handler: this._onApiGetDefaultAnkiFieldTemplates.bind(this)}],
|
||||||
['getDictionaryInfo', {async: true, contentScript: false, handler: this._onApiGetDictionaryInfo.bind(this)}],
|
['getDictionaryInfo', {async: true, contentScript: true, handler: this._onApiGetDictionaryInfo.bind(this)}],
|
||||||
['getDictionaryCounts', {async: true, contentScript: false, handler: this._onApiGetDictionaryCounts.bind(this)}],
|
['getDictionaryCounts', {async: true, contentScript: false, handler: this._onApiGetDictionaryCounts.bind(this)}],
|
||||||
['purgeDatabase', {async: true, contentScript: false, handler: this._onApiPurgeDatabase.bind(this)}],
|
['purgeDatabase', {async: true, contentScript: false, handler: this._onApiPurgeDatabase.bind(this)}],
|
||||||
['getMedia', {async: true, contentScript: true, handler: this._onApiGetMedia.bind(this)}],
|
['getMedia', {async: true, contentScript: true, handler: this._onApiGetMedia.bind(this)}],
|
||||||
@ -1288,10 +1288,14 @@ class Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_validatePrivilegedMessageSender(sender) {
|
_validatePrivilegedMessageSender(sender) {
|
||||||
const url = sender.url;
|
let {url} = sender;
|
||||||
if (!(typeof url === 'string' && yomichan.isExtensionUrl(url))) {
|
if (typeof url === 'string' && yomichan.isExtensionUrl(url)) { return; }
|
||||||
throw new Error('Invalid message sender');
|
const {tab} = url;
|
||||||
|
if (typeof tab === 'object' && tab !== null) {
|
||||||
|
({url} = tab);
|
||||||
|
if (typeof url === 'string' && yomichan.isExtensionUrl(url)) { return; }
|
||||||
}
|
}
|
||||||
|
throw new Error('Invalid message sender');
|
||||||
}
|
}
|
||||||
|
|
||||||
_getBrowserIconTitle() {
|
_getBrowserIconTitle() {
|
||||||
|
@ -83,9 +83,23 @@ class Environment {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
|
if (this._isSafari()) {
|
||||||
|
return 'safari';
|
||||||
|
}
|
||||||
return 'firefox';
|
return 'firefox';
|
||||||
} else {
|
} else {
|
||||||
return 'chrome';
|
return 'chrome';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_isSafari() {
|
||||||
|
const {vendor, userAgent} = navigator;
|
||||||
|
return (
|
||||||
|
typeof vendor === 'string' &&
|
||||||
|
typeof userAgent === 'string' &&
|
||||||
|
vendor.includes('Apple') &&
|
||||||
|
!userAgent.includes('CriOS') &&
|
||||||
|
!userAgent.includes('FxiOS')
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,18 +19,20 @@
|
|||||||
* TextToSpeechAudio
|
* TextToSpeechAudio
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class AudioSystem {
|
class AudioSystem extends EventDispatcher {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
this._fallbackAudio = null;
|
this._fallbackAudio = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare() {
|
prepare() {
|
||||||
// speechSynthesis.getVoices() will not be populated unless some API call is made.
|
// speechSynthesis.getVoices() will not be populated unless some API call is made.
|
||||||
if (typeof speechSynthesis === 'undefined') { return; }
|
if (
|
||||||
|
typeof speechSynthesis !== 'undefined' &&
|
||||||
const eventListeners = new EventListenerCollection();
|
typeof speechSynthesis.addEventListener === 'function'
|
||||||
const onVoicesChanged = () => { eventListeners.removeAllEventListeners(); };
|
) {
|
||||||
eventListeners.addEventListener(speechSynthesis, 'voiceschanged', onVoicesChanged, false);
|
speechSynthesis.addEventListener('voiceschanged', this._onVoicesChanged.bind(this), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFallbackAudio() {
|
getFallbackAudio() {
|
||||||
@ -64,6 +66,10 @@ class AudioSystem {
|
|||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
||||||
|
_onVoicesChanged(e) {
|
||||||
|
this.trigger('voiceschanged', e);
|
||||||
|
}
|
||||||
|
|
||||||
_isAudioValid(audio, source) {
|
_isAudioValid(audio, source) {
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case 'jpod101':
|
case 'jpod101':
|
||||||
|
@ -103,6 +103,10 @@ class DisplayController {
|
|||||||
let tab;
|
let tab;
|
||||||
try {
|
try {
|
||||||
tab = await this._getCurrentTab();
|
tab = await this._getCurrentTab();
|
||||||
|
// Safari assigns a tab object to the popup, other browsers do not
|
||||||
|
if (tab && await this._isSafari()) {
|
||||||
|
tab = void 0;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
@ -220,6 +224,11 @@ class DisplayController {
|
|||||||
node.hidden = false;
|
node.hidden = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _isSafari() {
|
||||||
|
const {browser} = await yomichan.api.getEnvironmentInfo();
|
||||||
|
return browser === 'safari';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -39,9 +39,7 @@ class AudioController {
|
|||||||
|
|
||||||
this._audioSourceAddButton.addEventListener('click', this._onAddAudioSource.bind(this), false);
|
this._audioSourceAddButton.addEventListener('click', this._onAddAudioSource.bind(this), false);
|
||||||
|
|
||||||
if (typeof speechSynthesis !== 'undefined') {
|
this._audioSystem.on('voiceschanged', this._updateTextToSpeechVoices.bind(this), false);
|
||||||
speechSynthesis.addEventListener('voiceschanged', this._updateTextToSpeechVoices.bind(this), false);
|
|
||||||
}
|
|
||||||
this._updateTextToSpeechVoices();
|
this._updateTextToSpeechVoices();
|
||||||
|
|
||||||
document.querySelector('#text-to-speech-voice-test').addEventListener('click', this._onTestTextToSpeech.bind(this), false);
|
document.querySelector('#text-to-speech-voice-test').addEventListener('click', this._onTestTextToSpeech.bind(this), false);
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
* DictionaryController
|
* DictionaryController
|
||||||
* DictionaryImportController
|
* DictionaryImportController
|
||||||
* DocumentFocusController
|
* DocumentFocusController
|
||||||
|
* Environment
|
||||||
* ExtensionKeyboardShortcutController
|
* ExtensionKeyboardShortcutController
|
||||||
* GenericSettingController
|
* GenericSettingController
|
||||||
* KeyboardShortcutController
|
* KeyboardShortcutController
|
||||||
@ -47,11 +48,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
async function setupEnvironmentInfo() {
|
async function setupEnvironmentInfo() {
|
||||||
|
const {dataset} = document.documentElement;
|
||||||
const {manifest_version: manifestVersion} = chrome.runtime.getManifest();
|
const {manifest_version: manifestVersion} = chrome.runtime.getManifest();
|
||||||
const {browser, platform} = await yomichan.api.getEnvironmentInfo();
|
dataset.manifestVersion = `${manifestVersion}`;
|
||||||
document.documentElement.dataset.browser = browser;
|
|
||||||
document.documentElement.dataset.os = platform.os;
|
const environment = new Environment();
|
||||||
document.documentElement.dataset.manifestVersion = `${manifestVersion}`;
|
await environment.prepare();
|
||||||
|
const {browser, platform} = environment.getInfo();
|
||||||
|
|
||||||
|
dataset.browser = browser;
|
||||||
|
dataset.os = platform.os;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupGenericSettingsController(genericSettingController) {
|
async function setupGenericSettingsController(genericSettingController) {
|
||||||
@ -67,9 +73,20 @@ async function setupGenericSettingsController(genericSettingController) {
|
|||||||
const statusFooter = new StatusFooter(document.querySelector('.status-footer-container'));
|
const statusFooter = new StatusFooter(document.querySelector('.status-footer-container'));
|
||||||
statusFooter.prepare();
|
statusFooter.prepare();
|
||||||
|
|
||||||
|
setupEnvironmentInfo();
|
||||||
|
|
||||||
|
let prepareTimer = setTimeout(() => {
|
||||||
|
prepareTimer = null;
|
||||||
|
document.documentElement.dataset.loadingStalled = 'true';
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
await yomichan.prepare();
|
await yomichan.prepare();
|
||||||
|
|
||||||
setupEnvironmentInfo();
|
if (prepareTimer !== null) {
|
||||||
|
clearTimeout(prepareTimer);
|
||||||
|
prepareTimer = null;
|
||||||
|
}
|
||||||
|
delete document.documentElement.dataset.loadingStalled;
|
||||||
|
|
||||||
const optionsFull = await yomichan.api.optionsGetFull();
|
const optionsFull = await yomichan.api.optionsGetFull();
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-browser="unknown" data-os="unknown" data-manifest-version="unknown">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
@ -59,6 +59,23 @@
|
|||||||
|
|
||||||
<h1>Yomichan Settings</h1>
|
<h1>Yomichan Settings</h1>
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
|
<div class="settings-group settings-group-top-margin page-loading-stalled-notification">
|
||||||
|
<div class="settings-item"><div class="settings-item-inner settings-item-inner-wrappable">
|
||||||
|
<div class="settings-item-left">
|
||||||
|
<div class="settings-item-label">
|
||||||
|
<p>
|
||||||
|
This page is taking longer than expected to load.
|
||||||
|
</p>
|
||||||
|
<p data-show-for-browser="safari">
|
||||||
|
Due to a bug in Safari, it may be necessary to click the <img src="/images/yomichan-icon.svg" class="inline-icon" alt=""> <em>Yomichan</em>
|
||||||
|
button in the browser bar to fully load the page
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Profile -->
|
<!-- Profile -->
|
||||||
<div class="heading-container">
|
<div class="heading-container">
|
||||||
<div class="heading-container-icon"><span class="icon" data-icon="profile"></span></div>
|
<div class="heading-container-icon"><span class="icon" data-icon="profile"></span></div>
|
||||||
|
Loading…
Reference in New Issue
Block a user