Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Force Google Docs to use HTML mode instead of canvas mode #596

Merged
merged 1 commit into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions extension/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ chrome.runtime.onMessage.addListener(async (request, sender, response) => {
}
rcxMain.onTabSelect(sender.tab.id);
break;
case 'forceDocsHtml?':
console.log('forceDocsHtml?');
if (rcxMain.enabled === 1) {
response(true);
chrome.tabs.sendMessage(sender.tab!.id!, {
type: 'showPopup',
text: `
rikaikun is forcing Google Docs to render using HTML instead of canvas.<br>
rikaikun can't work with canvas mode but if you need that mode, please disable rikaikun.
`,
});
}
break;
case 'xsearch':
console.log('xsearch');
response(rcxMain.search(request.text, request.dictOption));
Expand Down Expand Up @@ -70,3 +83,5 @@ chrome.runtime.onMessage.addListener(async (request, sender, response) => {
// Clear browser action badge text on first load
// Chrome preserves last state which is usually 'On'
chrome.browserAction.setBadgeText({ text: '' });

export { rcxMainPromise as TestOnlyRxcMainPromise };
23 changes: 23 additions & 0 deletions extension/docs-html-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
function forceHtml(force: boolean) {
if (!force) {
return;
}
console.log(
'rikaikun is forcing Docs to use HTML instead of canvas for rendering.'
);
const injectedCode = `(function() {window['_docs_force_html_by_ext'] = '${chrome.runtime.id}';})();`;

const script = document.createElement('script');

script.textContent = injectedCode;

// Usually, `document.head` isn't guaranteed to be present when content_scripts run but in this case
// we're running inside a callback so it should be 100% safe.
document.head.appendChild(script);
}

// This check allows the user to get newer Docs Canvas without disabling rikaikun.
// This delays when the forcing code is injected but it seems to be early enough in practice.
chrome.runtime.sendMessage({ type: 'forceDocsHtml?' }, forceHtml);

export { forceHtml as TestOnlyForceHtml };
6 changes: 6 additions & 0 deletions extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
"match_about_blank": true,
"js": ["rikaicontent.js"],
"all_frames": true
},
{
"matches": ["https://docs.google.com/*"],
"js": ["docs-html-fallback.js"],
"run_at": "document_start",
"all_frames": true
}
],
"web_accessible_resources": [
Expand Down
88 changes: 88 additions & 0 deletions extension/test/background_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { RcxMain } from '../rikaichan';
import { expect, use } from '@esm-bundle/chai';
import chrome from 'sinon-chrome';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';

use(sinonChai);

let rcxMain: RcxMain;

describe('background.ts', () => {
before(async () => {
// Resolve config fetch with minimal config object.
chrome.storage.sync.get.yields({ kanjiInfo: [] });
// Imports only run once so run in `before` to make it deterministic.
rcxMain = await (await import('../background')).TestOnlyRxcMainPromise;
});

beforeEach(() => {
// Only reset the spies we're using since we need to preserve
// the state of `chrome.runtime.onMessage.addListener` for invoking
// the core functionality of background.ts.
chrome.tabs.sendMessage.reset();
});

describe('when sent "forceDocsHtml?" message', () => {
it('should not call response callback when rikaikun disabled', async () => {
rcxMain.enabled = 0;
const responseCallback = sinon.spy();

await sendMessageToBackground({
type: 'forceDocsHtml?',
responseCallback: responseCallback,
});

expect(responseCallback).to.have.not.been.called;
});

it('should not send "showPopup" message when rikaikun disabled', async () => {
rcxMain.enabled = 0;

await sendMessageToBackground({
type: 'forceDocsHtml?',
});

expect(chrome.tabs.sendMessage).to.have.not.been.called;
});

it('should pass true to response callback when rikaikun enabled', async () => {
rcxMain.enabled = 1;
const responseCallback = sinon.spy();

await sendMessageToBackground({
type: 'forceDocsHtml?',
responseCallback: responseCallback,
});

expect(responseCallback).to.have.been.calledOnceWith(true);
});

it('should send "showPopup" message when rikaikun enabled', async () => {
rcxMain.enabled = 1;

await sendMessageToBackground({
type: 'forceDocsHtml?',
});

expect(chrome.tabs.sendMessage).to.have.been.calledWith(
/* tabId= */ sinon.match.any,
sinon.match({ type: 'showPopup' })
);
});
});
});

async function sendMessageToBackground({
type,
responseCallback = () => {},
}: {
type: string;
responseCallback?: Function;
}): Promise<void> {
return await chrome.runtime.onMessage.addListener.yield(
{ type: type },
{ tab: { id: 0 } },
responseCallback
);
}
40 changes: 40 additions & 0 deletions extension/test/docs-html-fallback_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { expect } from '@esm-bundle/chai';
import chrome from 'sinon-chrome';

declare global {
interface Window {
_docs_force_html_by_ext?: string;
}
}

let forceHtmlCallback: (force: boolean) => void;

describe('docs-html-fallback.ts after sending `forceDocsHtml?` message', () => {
before(async () => {
await import('../docs-html-fallback');
forceHtmlCallback = chrome.runtime.sendMessage.args[0][1];
});

beforeEach(() => {
chrome.reset();
window._docs_force_html_by_ext = undefined;
});

describe('when `forceHtml` callback is called with `false`', () => {
it('should not add special property to window object', async () => {
forceHtmlCallback(false);

expect(window._docs_force_html_by_ext).to.be.undefined;
});
});

describe('when `forceHtml` callback is called with `true`', () => {
it('should set special property to rikaikun extension ID', async () => {
chrome.runtime.id = 'test_special_id';

forceHtmlCallback(true);

expect(window._docs_force_html_by_ext).to.equal(chrome.runtime.id);
});
});
});
2 changes: 1 addition & 1 deletion snowpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const config = {
list: [
//Remove test only export from rikaicontent
{
from: /export.*TestOnlyRcxContent.*\n/,
from: /export.*TestOnly.*\n/,
to: '',
},
],
Expand Down