Add js-logic to the popup's pacChooser form. Remove code related to CopyUnicodeUrls

This commit is contained in:
ilyaigpetrov 2024-08-22 15:26:44 +05:00
parent bc75c74c8a
commit 0eb6194bc6
9 changed files with 127 additions and 255 deletions

View File

@ -1,6 +1,6 @@
{
"name": "runet-censorship-bypass",
"version": "0.2.0",
"version": "0.0.2",
"description": "",
"homepage": "https://github.com/ilyaigpetrov/copy-unicode-urls#readme",
"main": "index.js",
@ -20,7 +20,6 @@
},
"license": "ISC",
"dependencies": {
"punycode": "^2.1.1"
},
"devDependencies": {
"merge": "^2.1.1",

View File

@ -3,12 +3,8 @@ import { versions, storage } from '../lib/index.mjs';
globalThis.migrationPromise = new Promise(async (resolve) => {
console.log('Checking for migrations...');
const dflts = {
options: [
[ 'ifToDecode', true ],
[ 'ifToDecodeMultipleTimes', false ],
[ 'ifToEncodeUrlTerminators', true ],
],
donateUrl: 'https://rebrand.ly/ilya-donate',
// TODO: Define defaults.
options: {},
};
const ifEmpty = await storage.isEmptyAsync();
if (ifEmpty) {
@ -23,21 +19,20 @@ globalThis.migrationPromise = new Promise(async (resolve) => {
console.log(`Current extension version is ${versions.current}.`);
const oldVersion = await storage.getAsync('version');
const ifNoNeedToMigrate = oldVersion === versions.current;
if (ifNoNeedToMigrate) {http://я.рф/яhttp://я.рф/я
if (ifNoNeedToMigrate) {
console.log('No need for migration.');
return resolve();
}
console.log(`Migrating to ${versions.current} from ${oldVersion || 'a very old version'}.`);
switch(true) {
case !oldVersion: {
// Update from version <= 0.0.18.
const ifSentence = await storage.getAsync('ifToEncodeSentenceTerminators');
// Update from version <= 0.0.1.
const ifSentence = await storage.getAsync('SOME_KEY');
if (ifSentence !== undefined) {
console.log('Migrating to 0.0.18.');
console.log('Migrating to 0.0.1.');
await storage.setAsync({
ifToEncodeUrlTerminators: ifSentence,
});
await storage.removeAsync('ifToEncodeSentenceTerminators')
}
}; // Fallthrough.
case versions.isLeq(oldVersion, '0.0.18'): {

View File

@ -1,7 +1,13 @@
import { toUnicode } from '../../node_modules/punycode/punycode.es6.js';
import { copyToClipboardAsync } from '../lib/copy-to-clipboard.mjs';
import { storage } from '../lib/index.mjs';
console.log('Main script starts...');
(async () => {
const theState = await storage.getAsync();
console.log('The state is:', theState);
})();
/*
const ID_TO_MENU_HANDLER = {};
const createMenuEntry = (id, type, title, handler, contexts, rest) => {
@ -27,63 +33,6 @@ const copyUrlInstalledPromise = (async () => {
console.log('Migration is finished.');
const options = await storage.getAsync('options');
const getOpt = (key) => (options.find((el) => el[0] === key)[1]);
console.log('Options are:', options);
const localizeUrl = (url) => {
console.log('Localizing url:', url);
let u;
try {
u = new URL(url);
} catch {
u = new URL(`http://${url}`);
}
let newHref = u.href;
let oldHref;
do {
oldHref = newHref;
newHref = decodeURI(
newHref
.replace(u.hostname, toUnicode(u.hostname))
/*
Don't decode `%25` to `%` because it causes errors while being put in GitHub URLs.
Test case: https://github.com/ilyaigpetrov/copy-unicode-urls/wiki/Test-%25-and-%3F
*/
.replaceAll('%25', '%2525'),
)
// Encode whitespace.
.replace(
/\s/g,
(_, index, wholeString) => encodeURIComponent(wholeString.charAt(index)),
);
} while (getOpt('ifToDecodeMultipleTimes') && oldHref !== newHref);
console.log('Localized:', newHref);
return newHref;
};
const copyUrl = async (url) => {
if (getOpt('ifToDecode')) {
url = localizeUrl(url);
}
if (getOpt('ifToEncodeUrlTerminators')) {
console.log('Encoding terminators...');
/*
Issue #7.
Thunderbird sources:
https://searchfox.org/comm-central/source/mozilla/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp#281 (mozTXTToHTMLConv::FindURLEnd)
These chars terminate the URL: ><"`}{)]`
These sequence doesn't terminate the URL: //[ (e.g. http://[1080::...)
These chars are not allowed at the end of the URL: .,;!?-:'
I apply slightly more strict rules below.
**/
const toPercentCode = (char) => '%' + char.charCodeAt(0).toString(16).toUpperCase();
url = url.replace(
/(?:[<>{}()[\]"`']|[.,;:!?-]$)/g,
(matchedChar, index, wholeString) => toPercentCode(matchedChar),
);
}
copyToClipboardAsync(url);
};
// CheckBoxes
const capitalizeFirstLetter = (str) => str
@ -137,12 +86,12 @@ const copyUrlInstalledPromise = (async () => {
'copyHighlightLink', 'normal',
chrome.i18n.getMessage('CopyUnicodeLinkToHighlight'),
(info) => {
copyUrl(`${info.pageUrl.replace(/#.*/g, '')}#:~:text=${info.selectionText}`);
copyUrl(`${info.pageUrl.replace(/#.*\/g, '')}#:~:text=${info.selectionText}`);
},
['selection'],
);
return copyUrl;
return Promise.resolve();
})();
@ -162,5 +111,6 @@ chrome.action.onClicked.addListener(async ({ url: urlToBeCopied }) => {
console.log('Main waits for listeners to be installed...');
const copyUrl = await copyUrlInstalledPromise;
console.log('Action clicked with url:', urlToBeCopied);
copyUrl(urlToBeCopied);
//copyUrl(urlToBeCopied);
});
*/

View File

@ -1,5 +1,5 @@
import "./00-start.mjs";
import "./05-data-init-and-migrations.mjs";
import "./10-main.mjs";
import './00-start.mjs';
import './05-data-init-and-migrations.mjs';
import './10-main.mjs';
console.log('Background script finished loading.');

View File

@ -1,61 +0,0 @@
let IF_ALREADY_PROMISE; // A global promise to avoid concurrency issues.
const OFFSCREEN_DOC_PATH = '/src/lib/offscreen-doc-for-copying.html';
export const copyToClipboardAsync = async (copyMe) => {
console.log('Going to copy this url:', copyMe);
try {
console.log('Trying `navigator.clipboard`...');
return await navigator.clipboard.writeText(copyMe);
} catch(e) {
console.log('Got error while copying attempt:', e);
if (globalThis.document) {
console.log('Trying `globalThis.document`...');
const area = document.createElement('textarea');
area.value = copyMe;
document.body.appendChild(area);
area.select();
document.execCommand('copy');
document.body.removeChild(area);
return;
}
console.log('Trying `chrome.offscreen`...');
async function setupOffscreenDocument(path) {
// Check all windows controlled by the service worker to see if one
// of them is the offscreen document with the given path
const offscreenUrl = chrome.runtime.getURL(path);
const existingContexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [offscreenUrl],
});
console.log('Existing contexts:', existingContexts);
if (existingContexts.length > 0) {
console.log('Offscreen document already exists.');
return;
}
// create offscreen document
if (IF_ALREADY_PROMISE) {
return IF_ALREADY_PROMISE;
}
console.log('Creating new offscreen document for:', path);
IF_ALREADY_PROMISE = chrome.offscreen.createDocument({
url: path,
reasons: [chrome.offscreen.Reason.CLIPBOARD],
justification: 'reason for needing the document',
});
await IF_ALREADY_PROMISE;
console.log('New offscreen document created.');
IF_ALREADY_PROMISE = null;
}
await setupOffscreenDocument(OFFSCREEN_DOC_PATH);
// Now that we have an offscreen document, we can dispatch the
// message.
chrome.runtime.sendMessage({
type: 'copy-data-to-clipboard',
target: 'offscreen-doc',
data: copyMe,
});
}
};

View File

@ -1,3 +0,0 @@
<!DOCTYPE html>
<textarea id="text"></textarea>
<script src="./offscreen-doc-for-copying.mjs" type="module"></script>

View File

@ -1,74 +0,0 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Once the message has been posted from the service worker, checks are made to
// confirm the message type and target before proceeding. This is so that the
// module can easily be adapted into existing workflows where secondary uses for
// the document (or alternate offscreen documents) might be implemented.
// Registering this listener when the script is first executed ensures that the
// offscreen document will be able to receive messages when the promise returned
// by `offscreen.createDocument()` resolves.
chrome.runtime.onMessage.addListener(handleMessages);
// This function performs basic filtering and error checking on messages before
// dispatching the
// message to a more specific message handler.
async function handleMessages(message) {
// Return early if this message isn't meant for the offscreen document.
if (message.target !== 'offscreen-doc') {
return;
}
// Dispatch the message to an appropriate handler.
switch (message.type) {
case 'copy-data-to-clipboard':
handleClipboardWrite(message.data);
break;
default:
console.warn(`Unexpected message type received: '${message.type}'.`);
}
}
// We use a <textarea> element for two main reasons:
// 1. preserve the formatting of multiline text,
// 2. select the node's content using this element's `.select()` method.
const textEl = document.querySelector('#text');
// Use the offscreen document's `document` interface to write a new value to the
// system clipboard.
//
// At the time this demo was created (Jan 2023) the `navigator.clipboard` API
// requires that the window is focused, but offscreen documents cannot be
// focused. As such, we have to fall back to `document.execCommand()`.
async function handleClipboardWrite(data) {
try {
// Error if we received the wrong kind of data.
if (typeof data !== 'string') {
throw new TypeError(
`Value provided must be a 'string', got '${typeof data}'.`
);
}
// `document.execCommand('copy')` works against the user's selection in a web
// page. As such, we must insert the string we want to copy to the web page
// and to select that content in the page before calling `execCommand()`.
textEl.value = data;
textEl.select();
document.execCommand('copy');
} finally {
// Job's done! Close the offscreen document.
window.close();
}
}

View File

@ -40,11 +40,13 @@
same even after invertion. Also a flash/blink of colors
during invertion must be avoided.
*/
/*
background-color: white; /* Not transparent. */
color: black;
/*color: black;
/*
border: 0 none white;
outline: 0 none white;
color-scheme: light;
outline: 0 none white;*/
/*color-scheme: light;
/* COLOR INVERTION */
filter: invert(0); /* TODO: temporary disabled. */
}
@ -79,8 +81,8 @@
vertical-align: text-bottom;
}
input[type="url"] {
border-width: 0 0 1px 0;
border-color: crimson;
/*border-width: 0 0 1px 0;
border-color: crimson;*/
flex-grow: 1;
/*padding: 0 3px 0 3px;*/
/*width: 100%;*/
@ -88,6 +90,14 @@
input[type="radio"], label {
cursor: pointer;
}
#disabled:checked + label {
color: red;
}
div.nowrap {
/* Don't break sentences on spaces. */
white-space: nowrap;
}
/*
#own:checked + label:after {
content: ":";
@ -96,14 +106,17 @@
#own ~ div {
display: flex;
}
#disabled:checked + label {
color: crimson;
/*
input#own ~ div:has(> input#customPacUrl):after {
border: 5px solid lime;
background-color: navy;
content: "";
}
input#own ~ div:after:not(:empty) {
border: 5px solid pink;
}
*/
div.nowrap {
/* Don't break sentences on spaces. */
white-space: nowrap;
}
.use-preferred-color-scheme {
/*background-color: violet;
color: darkred;*/
@ -134,40 +147,53 @@
bottom: 0;
}
*/
/*
input:invalid {
border: 5px solid red;
}*/
#customPacUrl:disabled {
/*background-color: grey;*/
}
</style>
</head>
<body class="use-preferred-color-scheme">
<!--img src="./gsbg.png" class="gsbg"-->
<header>
PAC-script:
PAC-скрипт:
</header>
<nav>
<ul>
<li>
<input type="radio" value="antizapret" name="pacScript" id="antizapret">
<label for="antizapret">Антизапрет</label>
</li>
<li>
<input type="radio" value="anticensority" name="pacScript" id="anticensority">
<label for="anticensority">Антицензорити</label>
</li>
<li>
<input type="radio" value="own" name="pacScript" id="own">
<label for="own">Свой:</label>
<div>
<input type="url" placeholder="https://example.com/proxy.pac">
<button>Save</button>
</div>
</li>
<li>
<input type="radio" value="disabled" name="pacScript" id="disabled" checked>
<label for="disabled">Отключить / сброс</label>
</li>
</ul>
<div class="nowrap">
<input type="checkbox" name="reset" id="reset" checked>
<label for="reset">Сбрасывать перед переключением</label>
</div>
<form id="pacChooserForm">
<ul>
<li>
<input type="radio" value="antizapret" name="pacScript" id="antizapret">
<label for="antizapret">Антизапрет</label>
</li>
<li>
<input type="radio" value="anticensority" name="pacScript" id="anticensority">
<label for="anticensority">Антицензорити</label>
</li>
<li>
<input type="radio" value="own" name="pacScript" id="own" disabled>
<label for="own">Свой:</label>
<div>
<input id="customPacUrl" type="url" placeholder="https://example.com/proxy.pac"
size="27"
spellcheck="false" autocorrect="off" autocapitalize="off"
disabled required
>
<button id="editPacUrlButton" title="Редактировать">🖉</button>
</div>
</li>
<li>
<input type="radio" value="disabled" name="pacScript" id="disabled" checked>
<label for="disabled">Отключить / сброс</label>
</li>
</ul>
<div class="nowrap">
<input type="checkbox" name="reset" id="reset" checked>
<label for="reset">Сбрасывать перед переключением</label>
</div>
</form>
</nav>
<footer style="text-align: center">
<a id="donate" target="_blank" data-localize="__MSG_Donate__"

View File

@ -1,3 +1,41 @@
console.log('Options page is opening...');
pacChooserForm.addEventListener('change', function (event) {
console.log('ON CHANGE:', event);
pacChooserForm.reportValidity();
});
pacChooserForm.addEventListener('formdata', (event) => {
event.preventDefault();
console.log('ON FORMDATA', event);
return false; // Prevent default action.
});
editPacUrlButton.onclick = function (event) {
event.preventDefault();
const lockUrl = () => { customPacUrl.disabled = true; };
const unlockUrl = () => { customPacUrl.disabled = false; };
const ifUrlLocked = customPacUrl.disabled;
if (ifUrlLocked) {
unlockUrl();
return false;
}
const ifUrlValid = customPacUrl.checkValidity();
if (ifUrlValid) {
lockUrl();
own.disabled = false;
// TODO: Save to storage.
return false;
}
// Empty or incorrect url.
own.disabled = true; // `own.checked` doesn't matter here.
const ifUrlEmpty = !customPacUrl.value;
if (ifUrlEmpty) {
lockUrl();
return false;
}
return false;
};
/*
import { storage } from '../../lib/common-apis.mjs';
@ -18,6 +56,8 @@ options.forEach(([key, value], i) => {
};
});
*/
await chrome.storage.local.get('options');
const textElements = document.querySelectorAll('[data-localize]');
textElements.forEach((e) => {
const ref = e.dataset.localize;