Skip to content

Commit

Permalink
feat: Force Google Docs to use HTML mode instead of canvas mode
Browse files Browse the repository at this point in the history
Google Docs new canvas mode hides all text/dom from rikaikun and thus for rikaikun to work Google has provided a workaround.

Fixes #593
  • Loading branch information
melink14 committed Jul 12, 2021
1 parent 8d2c396 commit 0359502
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 1 deletion.
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
);
}
41 changes: 41 additions & 0 deletions extension/test/docs-html-fallback_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//import '../background';
import { expect, use } 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

0 comments on commit 0359502

Please sign in to comment.