Persistent display mode (#714)

* Simplify calls using chrome.tabs.sendMessage and getMessageResponseResult

* Rename message handlers

* Move onMessage handler into Display

* Assign search popup mode which persists across refreshes

* Update clipboard monitor on the search page

* Remove mode param
This commit is contained in:
toasted-nutbread 2020-08-09 13:11:41 -04:00 committed by GitHub
parent 04d47bf8a9
commit 8ee717cdf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 152 additions and 103 deletions

View File

@ -899,24 +899,8 @@ class Backend {
); );
}); });
if (tab !== null) { if (tab !== null) {
const isValidTab = await new Promise((resolve) => { const url = await this._getTabUrl(tabId);
chrome.tabs.sendMessage( const isValidTab = (url !== null && url.startsWith(baseUrl));
tabId,
{action: 'getUrl', params: {}},
{frameId: 0},
(response) => {
let result = false;
try {
const {url} = yomichan.getMessageResponseResult(response);
result = url.startsWith(baseUrl);
} catch (e) {
// NOP
}
resolve(result);
}
);
});
// windowId
if (isValidTab) { if (isValidTab) {
return {tab, created: false}; return {tab, created: false};
} }
@ -935,7 +919,7 @@ class Backend {
const popupWindow = await new Promise((resolve, reject) => { const popupWindow = await new Promise((resolve, reject) => {
chrome.windows.create( chrome.windows.create(
{ {
url: `${baseUrl}?mode=popup`, url: baseUrl,
width: popupWidth, width: popupWidth,
height: popupHeight, height: popupHeight,
type: 'popup' type: 'popup'
@ -959,23 +943,22 @@ class Backend {
const tab = tabs[0]; const tab = tabs[0];
await this._waitUntilTabFrameIsReady(tab.id, 0, 2000); await this._waitUntilTabFrameIsReady(tab.id, 0, 2000);
await this._sendMessageTab(
tab.id,
{action: 'setMode', params: {mode: 'popup'}},
{frameId: 0}
);
this._searchPopupTabId = tab.id; this._searchPopupTabId = tab.id;
return {tab, created: true}; return {tab, created: true};
} }
_updateSearchQuery(tabId, text, animate) { _updateSearchQuery(tabId, text, animate) {
return new Promise((resolve, reject) => { return this._sendMessageTab(
const callback = (response) => { tabId,
try { {action: 'updateSearchQuery', params: {text, animate}},
resolve(yomichan.getMessageResponseResult(response)); {frameId: 0}
} catch (error) { );
reject(error);
}
};
const message = {action: 'updateSearchQuery', params: {text, animate}};
chrome.tabs.sendMessage(tabId, message, callback);
});
} }
_sendMessageAllTabs(action, params={}) { _sendMessageAllTabs(action, params={}) {
@ -1381,18 +1364,20 @@ class Backend {
return typeof templates === 'string' ? templates : this._defaultAnkiFieldTemplates; return typeof templates === 'string' ? templates : this._defaultAnkiFieldTemplates;
} }
_getTabUrl(tab) { async _getTabUrl(tabId) {
return new Promise((resolve) => {
chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => {
let url = null;
try { try {
({url} = yomichan.getMessageResponseResult(response)); const {url} = await this._sendMessageTab(
} catch (error) { tabId,
{action: 'getUrl', params: {}},
{frameId: 0}
);
if (typeof url === 'string') {
return url;
}
} catch (e) {
// NOP // NOP
} }
resolve({tab, url}); return null;
});
});
} }
async _findTab(timeout, checkUrl) { async _findTab(timeout, checkUrl) {
@ -1538,4 +1523,18 @@ class Backend {
} }
return await (json ? response.json() : response.text()); return await (json ? response.json() : response.text());
} }
_sendMessageTab(...args) {
return new Promise((resolve, reject) => {
const callback = (response) => {
try {
resolve(yomichan.getMessageResponseResult(response));
} catch (error) {
reject(error);
}
};
chrome.tabs.sendMessage(...args, callback);
});
}
} }

View File

@ -37,6 +37,7 @@ class DisplaySearch extends Display {
this._clipboardMonitor = new ClipboardMonitor({ this._clipboardMonitor = new ClipboardMonitor({
getClipboard: api.clipboardGet.bind(api) getClipboard: api.clipboardGet.bind(api)
}); });
this._clipboardMonitorEnabled = false;
this._onKeyDownIgnoreKeys = new Map([ this._onKeyDownIgnoreKeys = new Map([
['ANY_MOD', new Set([ ['ANY_MOD', new Set([
'Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'PageDown', 'PageUp', 'Home', 'End', 'Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'PageDown', 'PageUp', 'Home', 'End',
@ -51,9 +52,6 @@ class DisplaySearch extends Display {
['AltGraph', new Set()], ['AltGraph', new Set()],
['Shift', new Set()] ['Shift', new Set()]
]); ]);
this._runtimeMessageHandlers = new Map([
['updateSearchQuery', {async: false, handler: this._onExternalSearchUpdate.bind(this)}]
]);
} }
async prepare() { async prepare() {
@ -62,18 +60,16 @@ class DisplaySearch extends Display {
yomichan.on('optionsUpdated', () => this.updateOptions()); yomichan.on('optionsUpdated', () => this.updateOptions());
this.on('contentUpdating', this._onContentUpdating.bind(this)); this.on('contentUpdating', this._onContentUpdating.bind(this));
this.on('modeChange', this._onModeChange.bind(this));
this.registerMessageHandlers([
['updateSearchQuery', {async: false, handler: this._onExternalSearchUpdate.bind(this)}]
]);
this.queryParserVisible = true; this.queryParserVisible = true;
this.setHistorySettings({useBrowserHistory: true}); this.setHistorySettings({useBrowserHistory: true});
const options = this.getOptions(); const options = this.getOptions();
const urlSearchParams = new URLSearchParams(location.search);
let mode = urlSearchParams.get('mode');
if (mode === null) { mode = ''; }
document.documentElement.dataset.searchMode = mode;
if (options.general.enableWanakana === true) { if (options.general.enableWanakana === true) {
this._wanakanaEnable.checked = true; this._wanakanaEnable.checked = true;
wanakana.bind(this._query); wanakana.bind(this._query);
@ -81,25 +77,15 @@ class DisplaySearch extends Display {
this._wanakanaEnable.checked = false; this._wanakanaEnable.checked = false;
} }
if (mode !== 'popup') {
if (options.general.enableClipboardMonitor === true) {
this._clipboardMonitorEnable.checked = true;
this._clipboardMonitor.start();
} else {
this._clipboardMonitorEnable.checked = false;
}
this._clipboardMonitorEnable.addEventListener('change', this._onClipboardMonitorEnableChange.bind(this));
}
chrome.runtime.onMessage.addListener(this._onRuntimeMessage.bind(this));
this._search.addEventListener('click', this._onSearch.bind(this), false); this._search.addEventListener('click', this._onSearch.bind(this), false);
this._query.addEventListener('input', this._onSearchInput.bind(this), false); this._query.addEventListener('input', this._onSearchInput.bind(this), false);
this._wanakanaEnable.addEventListener('change', this._onWanakanaEnableChange.bind(this)); this._wanakanaEnable.addEventListener('change', this._onWanakanaEnableChange.bind(this));
window.addEventListener('copy', this._onCopy.bind(this)); window.addEventListener('copy', this._onCopy.bind(this));
this._clipboardMonitor.on('change', this._onExternalSearchUpdate.bind(this)); this._clipboardMonitor.on('change', this._onExternalSearchUpdate.bind(this));
this._clipboardMonitorEnable.addEventListener('change', this._onClipboardMonitorEnableChange.bind(this));
this._updateSearchButton(); this._updateSearchButton();
this._onModeChange();
await this._prepareNestedPopups(); await this._prepareNestedPopups();
@ -209,12 +195,6 @@ class DisplaySearch extends Display {
this._onSearchQueryUpdated(query, true); this._onSearchQueryUpdated(query, true);
} }
_onRuntimeMessage({action, params}, sender, callback) {
const messageHandler = this._runtimeMessageHandlers.get(action);
if (typeof messageHandler === 'undefined') { return false; }
return yomichan.invokeMessageHandler(messageHandler, params, callback, sender);
}
_onCopy() { _onCopy() {
// ignore copy from search page // ignore copy from search page
this._clipboardMonitor.setPreviousText(window.getSelection().toString().trim()); this._clipboardMonitor.setPreviousText(window.getSelection().toString().trim());
@ -261,34 +241,15 @@ class DisplaySearch extends Display {
} }
_onClipboardMonitorEnableChange(e) { _onClipboardMonitorEnableChange(e) {
if (e.target.checked) { const enabled = e.target.checked;
chrome.permissions.request( this._setClipboardMonitorEnabled(enabled);
{permissions: ['clipboardRead']},
(granted) => {
if (granted) {
this._clipboardMonitor.start();
api.modifySettings([{
action: 'set',
path: 'general.enableClipboardMonitor',
value: true,
scope: 'profile',
optionsContext: this.getOptionsContext()
}], 'search');
} else {
e.target.checked = false;
}
}
);
} else {
this._clipboardMonitor.stop();
api.modifySettings([{
action: 'set',
path: 'general.enableClipboardMonitor',
value: false,
scope: 'profile',
optionsContext: this.getOptionsContext()
}], 'search');
} }
_onModeChange() {
let mode = this.mode;
if (mode === null) { mode = ''; }
document.documentElement.dataset.searchMode = mode;
this._updateClipboardMonitorEnabled();
} }
_isWanakanaEnabled() { _isWanakanaEnabled() {
@ -381,4 +342,48 @@ class DisplaySearch extends Display {
await onOptionsUpdated(); await onOptionsUpdated();
} }
async _setClipboardMonitorEnabled(value) {
let modify = true;
if (value) {
value = await this._requestPermissions(['clipboardRead']);
modify = value;
}
this._clipboardMonitorEnabled = value;
this._updateClipboardMonitorEnabled();
if (!modify) { return; }
await api.modifySettings([{
action: 'set',
path: 'general.enableClipboardMonitor',
value,
scope: 'profile',
optionsContext: this.getOptionsContext()
}], 'search');
}
_updateClipboardMonitorEnabled() {
const mode = this.mode;
const enabled = this._clipboardMonitorEnabled;
this._clipboardMonitorEnable.checked = enabled;
if (enabled && mode !== 'popup') {
this._clipboardMonitor.start();
} else {
this._clipboardMonitor.stop();
}
}
_requestPermissions(permissions) {
return new Promise((resolve) => {
chrome.permissions.request(
{permissions},
(granted) => {
const e = chrome.runtime.lastError;
resolve(!e && granted);
}
);
});
}
} }

View File

@ -44,7 +44,7 @@ class DisplayFloat extends Display {
async prepare() { async prepare() {
await super.prepare(); await super.prepare();
this.registerMessageHandlers([ this.registerDirectMessageHandlers([
['configure', {async: true, handler: this._onMessageConfigure.bind(this)}], ['configure', {async: true, handler: this._onMessageConfigure.bind(this)}],
['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}] ['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}]
]); ]);

View File

@ -65,6 +65,7 @@ class Display extends EventDispatcher {
this._hotkeys = new Map(); this._hotkeys = new Map();
this._actions = new Map(); this._actions = new Map();
this._messageHandlers = new Map(); this._messageHandlers = new Map();
this._directMessageHandlers = new Map();
this._history = new DisplayHistory({clearable: true, useBrowserHistory: false}); this._history = new DisplayHistory({clearable: true, useBrowserHistory: false});
this._historyChangeIgnore = false; this._historyChangeIgnore = false;
this._historyHasChanged = false; this._historyHasChanged = false;
@ -80,6 +81,7 @@ class Display extends EventDispatcher {
getOptionsContext: this.getOptionsContext.bind(this), getOptionsContext: this.getOptionsContext.bind(this),
setSpinnerVisible: this.setSpinnerVisible.bind(this) setSpinnerVisible: this.setSpinnerVisible.bind(this)
}); });
this._mode = null;
this.registerActions([ this.registerActions([
['close', () => { this.onEscape(); }], ['close', () => { this.onEscape(); }],
@ -114,6 +116,9 @@ class Display extends EventDispatcher {
{key: 'V', modifiers: ['alt'], action: 'viewNote'} {key: 'V', modifiers: ['alt'], action: 'viewNote'}
]); ]);
this.registerMessageHandlers([ this.registerMessageHandlers([
['setMode', {async: false, handler: this._onMessageSetMode.bind(this)}]
]);
this.registerDirectMessageHandlers([
['setOptionsContext', {async: false, handler: this._onMessageSetOptionsContext.bind(this)}], ['setOptionsContext', {async: false, handler: this._onMessageSetOptionsContext.bind(this)}],
['setContent', {async: false, handler: this._onMessageSetContent.bind(this)}], ['setContent', {async: false, handler: this._onMessageSetContent.bind(this)}],
['clearAutoPlayTimer', {async: false, handler: this._onMessageClearAutoPlayTimer.bind(this)}], ['clearAutoPlayTimer', {async: false, handler: this._onMessageClearAutoPlayTimer.bind(this)}],
@ -138,7 +143,12 @@ class Display extends EventDispatcher {
this._updateQueryParserVisibility(); this._updateQueryParserVisibility();
} }
get mode() {
return this._mode;
}
async prepare() { async prepare() {
this._updateMode();
this._setInteractive(true); this._setInteractive(true);
await this._displayGenerator.prepare(); await this._displayGenerator.prepare();
await this._queryParser.prepare(); await this._queryParser.prepare();
@ -146,8 +156,9 @@ class Display extends EventDispatcher {
this._history.on('stateChanged', this._onStateChanged.bind(this)); this._history.on('stateChanged', this._onStateChanged.bind(this));
this._queryParser.on('searched', this._onQueryParserSearch.bind(this)); this._queryParser.on('searched', this._onQueryParserSearch.bind(this));
yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this));
chrome.runtime.onMessage.addListener(this._onMessage.bind(this));
api.crossFrame.registerHandlers([ api.crossFrame.registerHandlers([
['popupMessage', {async: 'dynamic', handler: this._onMessage.bind(this)}] ['popupMessage', {async: 'dynamic', handler: this._onDirectMessage.bind(this)}]
]); ]);
} }
@ -313,6 +324,12 @@ class Display extends EventDispatcher {
} }
} }
registerDirectMessageHandlers(handlers) {
for (const [name, handlerInfo] of handlers) {
this._directMessageHandlers.set(name, handlerInfo);
}
}
async setupNestedPopups(frontendInitializationData) { async setupNestedPopups(frontendInitializationData) {
await dynamicLoader.loadScripts([ await dynamicLoader.loadScripts([
'/mixed/js/text-scanner.js', '/mixed/js/text-scanner.js',
@ -343,10 +360,16 @@ class Display extends EventDispatcher {
// Message handlers // Message handlers
_onMessage(data) { _onMessage({action, params}, sender, callback) {
const messageHandler = this._messageHandlers.get(action);
if (typeof messageHandler === 'undefined') { return false; }
return yomichan.invokeMessageHandler(messageHandler, params, callback, sender);
}
_onDirectMessage(data) {
data = this.authenticateMessageData(data); data = this.authenticateMessageData(data);
const {action, params} = data; const {action, params} = data;
const handlerInfo = this._messageHandlers.get(action); const handlerInfo = this._directMessageHandlers.get(action);
if (typeof handlerInfo === 'undefined') { if (typeof handlerInfo === 'undefined') {
throw new Error(`Invalid action: ${action}`); throw new Error(`Invalid action: ${action}`);
} }
@ -356,6 +379,10 @@ class Display extends EventDispatcher {
return {async, result}; return {async, result};
} }
_onMessageSetMode({mode}) {
this._setMode(mode, true);
}
_onMessageSetOptionsContext({optionsContext}) { _onMessageSetOptionsContext({optionsContext}) {
this.setOptionsContext(optionsContext); this.setOptionsContext(optionsContext);
} }
@ -1253,4 +1280,22 @@ class Display extends EventDispatcher {
_closePopups() { _closePopups() {
yomichan.trigger('closePopups'); yomichan.trigger('closePopups');
} }
_updateMode() {
const mode = sessionStorage.getItem('mode');
this._setMode(mode, false);
}
_setMode(mode, save) {
if (mode === this._mode) { return; }
if (save) {
if (mode === null) {
sessionStorage.removeItem('mode');
} else {
sessionStorage.setItem('mode', mode);
}
}
this._mode = mode;
this.trigger('modeChange', {mode});
}
} }