javascript
Мир в бутылке: сжатие в картинку
Вчера я писал, что код и вероятности перехода в цепях Маркова упакованы в картинку, но не объяснил что это и для чего сделано.
Про эту технику несколько раз писали, но она остаётся малоизвестной. Суть в том, что мы, пользуясь тем, что любая картинка — формат со сжатием, кодируем в её пикселях текст нашей программы.
Это позволяет,
Сжатие при этом реализовывать в ДжаваСкрипте не нужно — браузер за нас распаковывается картинку, а мы её просто попиксельно читаем.
Поскольку веса для цепей Маркова занимают аж 3,5 мегабайта в джейсоне, я воспользовался этой идеей, чтобы поэффективнее его ужать, бинарно сериализуя его в картинку, а потом затянул туда и остальное — основной код и спрайты.
Развернуть видео
Иногда попадается зеркально отображённое видео — я думаю, таким образом залитые ролики пытаются скрыть от правообладателей. Мне
Его надо выполнять в консоли браузера, которая вызывается через «Инструменты разработчика».
document.querySelectorAll('*,iframe').forEach(e=>(e?.shadowRoot||e).
querySelectorAll('video').forEach(v=>v.style.transform = 'scaleX(-1)'))30 лет серверному ДжаваСкрипту!
Поздравляю всех фронтендеров с юбилеем серверного ДжаваСкрипта — ровно тридцать лет назад вышел Netscape Enterprise Server 2.0 с поддержкой технологии SSJS (
Код можно было писать прямо внутри страницы (как в ПХП, например), оборачивая куски, выполняемые на сервере при помощи тега SERVER:
<SERVER>
write("Click a link to display an image:<P>")
cursor = database.cursor("select * from rockStarBios")
while(cursor.next()) {
write(cursor.photos.blobLink("image/gif", "Image " + cursor.id))
write("<BR>")
}
cursor.close()
</SERVER>Позднее он на этом поле проиграл конкуренцию «Микрософту» и их технологии «Активных Серверных Страниц» (ASP), где можно было программировать на JScript (был такой диалект «ДжаваСкрипта») или VBScript и исчез с радаров.
Добавлено: оказывается я сам эту дату и раскопал, и даже в «Википедию» добавил.
Какой у вас GPU
Я тут случайно узнал, что через WebGL, оказывается, можно определить какой графический ускоритель используется на машине, где запущен браузер. Окно я стилизовал просто потому что просто так выводить было скучно. Посмотрите что у вас там написано.
Технически берётся параметр UNMASKED_RENDERER_WEBGL у контекста объекта WebGL.
У меня тут сейчас написано следующее:
ANGLE (Apple, ANGLE Metal Renderer: Apple M3 Max, Unspecified Version)
У братишки на рабочем компьютере (там «Виндоуз»):
ANGLE (Intel, Intel(R) UHD Graphics 770 (0x0000A780) Direct3D11 vs_5_0 ps_5_0, D3D11)
На моём смартфоне «Сяоми 14 Ультра»:
Adreno (TM) 750
Добавлено: как справедливо заметили в комментариях в телеграмном канале, это странно, что браузер позволяет так точно определить эту информацию, посколько производители браузеров часто борются с идентификацией браузера, а тут информация, которая позволяет производить идентификацию точнее.
JavaScript™
Поразительно! Сегодня я узнал, что «JavaScript» — зарегистрированная торговая марка, принадлежащая компании «Оракл». Она попала к «Ораклу» вместе с другими активами компании «Сан Микросистемс» при её поглощении
Большое везение, что «Оракл» до сих пор не попыталась
Автор Node.js пытается добиться отмены регистрации торговой марки, аргументируя это повсеместным использованием этого названия. Прецеденты есть, насколько я знаю, может и получиться.
Другим выходом из ситуации было бы ещё переименование языка в EMCAScript или в LiveScript — оба названия имеют отношение в ДжаваСкрипту. Но этот путь, как мне представляется, сложнее первого.
Атрибут LANGUAGE
Просматривал тут публичные репозитории некого соискателя одной из наших вакансий, обратил внимание, что у него там, помимо всего прочего, лежат решённые задания со «Стэпика». Заглянул из любопытства, никаких особенных выводов не сделал, но удивился, что в тегах одного задания у него script содержит атрибут language. Это что же, его до сих пор учат писать?
Мне стало любопытно — он вообще сейчас
Проверил. В документации он считается устаревшим, но если подставить туда неизвестное браузеру значение, скрипт не выполняется. В принципе, это логично. В интернете вполне могут встречаться ещё и древние сайты, использующие другие языки, зачем выполнять то, что браузер выполнить не может?
А что же считается верным значением для ДжаваСкрипта? В угадайку играть не хотелось, скачал исходники «Хромиума», посмотрел что там используется. Как оказалось, подставить можно следующие значения (регистр значения не имеет):
- javascript или x-javascript — это понятно, единственный сюрприз, что можно поставить «экс» перед словом;
- ecmascript или x-ecmascript — тоже понятно, ДжаваСкрипт — расширение этого языка;
- jscript — диалект языка в «Эксплорере», считается, что другие браузеры сейчас его тоже «знают», хотя на деле есть нюансы, но всем уже всё равно;
- от javascript1.0 до javascript1.5 —
когда-то указанием версии можно было разделять скрипты по уровню поддержки языка в браузере; - livescript — старое название языка «ДжаваСкрипта», я ещё помню времена, когда это значение использовалось, хотя и редко.
Сейчас, конечно, это скорее археология, чем
DoomPDF
Четверть века назад я писал о «Думе», запущенном на фотоаппарате. Тогда меня это очень удивило и восхитило. Но с тех пор много времени утекло, «Дум» на чём только не запускали и это удивляло всё меньше. Не помню когда в последний раз, пробегая глазами подобную новость, я думал хотя бы «надо же».
Но «Дум», запущенный внутри PDF-файла, меня чертовски удивил! Не то, чтобы я не знал, что это возможно —
Вы попробуйте, работает даже внутри браузера!
Правда, в данном случае PostScript почти не использовался. Всё дело в том, что внутри PDF есть возможность использовать ещё и JavaScript. Причём, в случае Adobe Acrobat, доступны, например, такие, сомнительные для PDF возможности, как
В просмотрщиках PDF современных браузеров поддержка JavaScript тоже есть, правда она сильно ограничена по функционалу. Тем не менее, для запуска «Дума» хватило и этого.
Чтобы запустить «Дум» внутри PDF, использовался старый Emscripten, который компилирует код на Си в asm.js (это такое подмножество JavaScript), вместо более привычного WebAssembly. Но этого недостаточно — нужно было придумать, как принимать ввод с клавиатуры и отображать графику. Это оказалось не таким уж и простым. К счастью, в браузерах PDF-движки поддерживают текстовые поля и кнопки, что и было использовано для ввода.
С графикой возникли следующие проблемы. Речь идет о разрешении 320×200 пикселей, что означает, что если бы использовались обычные текстовые поля для каждого пикселя, пришлось бы обновлять тысячи полей на каждый кадр. В общем, так бы вы никогда не поиграли в «Дум». Вместо этого автор решил использовать одно текстовое поле на каждую строку экрана и обновлять их с помощью ASCII-символов. Простой, но довольно эффективный способ, который дает в итоге монохромную картинку с шестью цветами. Конечно, такой способ тоже не даёт плавного рендера картинки, но играть всё равно довольно комфортно!
UnSnow 2
Полтора десятилетия назад я написал скрипт «UnSnow», выключающий снежинки, которыми иногда украшают сайты. Мне очень нравится Новый год и новогодняя движуха, но эти снежинки частенько раздражают, тем более, что их нередко делают крупными и навязчивыми.
За столько лет способы, которыми фронтендеры добавляют на сайт снежинки, сильно изменились, поэтому прежний скрипт перестал работать, но снежинки никуда не делись. Так как за эти годы никто не поднял упавшее знамя борьбы с визуальным безумием, придётся мне.
Новая версия скрипта можем замедлять некоторые сайты, зато, кажется, справляется с любыми случаями падения вкуса. В браузер скрипт можно установить при помощи плагина Tampermonkey или любого другого, умеющего выполнять UserScript.
// ==UserScript==
// @name UnSnow
// @author Evgeny Stepanischev aka Bolk
// @version 2024-12-31
// @namespace /files/unsnow2.js
// @description Turn off stupid snow
// @match *://*/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
if (window.location.hostname === 'aliexpress.ru') {
return;
}
Array.from(document.querySelectorAll('*')).forEach(e => {
for (let a of e.attributes) {
if (/(?<!un)snow/i.test(a.name + a.value)) {
e.style = 'visibility:hidden;position:absolute;margin-left:-10000px;' +
'display:none;transform:translateX(-100vw);opacity:0';
}
}
});
})();Посещал ли пользователь сайт?
Атака не работает — в браузерах давно реализована защита, у меня идея «сработала» в силу того, что я не выполнил все необходимые проверки.
В прошлом было достаточно много методов определить ходил ли пользовать
Это считалось проблемой, поэтому свойство изменили так, чтобы никакой утечки информации о посещении в ДжаваСкрипт не происходило. Все современные браузеры уже много лет от этого защищены.
Но недавно меня осенило, что есть способ обойти эту защиту!
Ведь если пользователь посещал целевой сайт, то скорее всего
Я попробовал сделать это с «Гитхабом». Самый большой файл, который загружается там с первой страницей — vendors-node_modules_github_mini-throttle_dist_index_js-node_modules_primer_octicons-react_di-b40d97-9b98c5140e22.js. Занимает он 136 килобайт, вполне подходящий размер.
Как же определить момент когда он загрузится? Если посмотреть внутрь, видно, что он после скачивания создаёт свойство webpackChunk в объекте globalThis. Если перехватить обращение к этому свойству, можно понять, что файл загружен:
const t = new Date();
Object.defineProperty(globalThis, 'webpackChunk', {
set: function() {
console.log(new Date() - t);
}
});Ниже этого кода подключаем загрузку указанного файла прямо с «Гитхаба» через тег <script> и дело в шляпе.
Я проверил, если очистить кеш браузера, то файл у меня загружается примерно за 100—400 миллисекунд, если берётся из кеша — это 0—12. Легко отличить одно от другого.
Сложности проверки версий
Случайно обратил внимание
Дело в том, что в движке, который я использую для своего дневника, для проигрывания аудио используется библиотека «Жуэль», а её текущая версия, в свою очередь использует старую версию библиотеки «howler.js», в которой есть вот такой код:
self._navigator = (typeof window !== 'undefined' && window.navigator) ? window.navigator : null;
var checkOpera = self._navigator && self._navigator.userAgent.match(/OPR\/([0-6].)/g);
var isOldOpera = (checkOpera && parseInt(checkOpera[0].split('/')[1], 10) < 33);Проблема в регулярном выражении OPR\/([0-6].) и в версии «Оперы». Недавно версия перевалила за 100 и эта регулярка, вместо корректного номера, стала возвращать «10».
Заменил у себя на сайте howler.js на актуальную версию и всё заработало. Заодно предложил патч в «Жуэль», надеюсь примут оперативно.
Какие ещё графические форматы поддерживает ваш браузер?
Переложил на хостинг «Гитхаба» свой проект семилетней давности — «Какие графические форматы поддерживает ваш браузер?». Я его иногда обновляю и мне было бы проще это делать через репозиторий.
Из всего, что я там проверяю, сразу не сработала только проверка на AVIF — это новейший формат, у меня на компьютере его поддерживает пока только «Файерфокс», ему важно указание типа содержимого, а «Гитхаб» передаёт неправильное значение.
Я подумал, что наверняка есть
Пришлось идти другим путём — вместо того, чтобы загружать файл картинки, внедрил его внутрь при помощи протокола data:, это позволило обойти проблему. Можете протестировать на своём браузере.
Ссылка на проект: https://bolknote.github.io/detect-browser-graphics-formats/
Ссылка не репозиторий проекта: https://github.com/bolknote/detect-browser-graphics-formats
JUnix в книге
Пока ещё далеко не ушёл от этой темы, напишу.
Недавно выкладывал в открытый доступ свой проект начала нулевых — JUnix, «эмулятор» Линукса на ДжаваСкрипте. Заодно для интереса погуглил много ли ссылок осталось на прежний сайт проекта — junix.kzn.ru.
Ссылок оказалось немало, заодно нагуглилось несколько статей, которых я не видел, когда проект был жив. Но удивительнее всего было встретить упоминание моего «эмулятора» в бумажной книге — учебном пособии по операционным системам.
Книга, как оказалось, всё ещё выпускается и продаётся, правда есть ли в переизданиях упоминание «ДжУникса» выяснить не удалось —
К слову, это не первая книжка, на страницы которой попал «ДжУникс». Первой была моя зачётная книжка. Поскольку во время увлечения этим проектом к учёбе я относился с прохладцей, пришлось совмещать приятное с полезным, и защищать «эмулятор» в качестве курсовой — как среду для обучения студентов операционной системе «Линукс».
Точную тему не помню, надо будет проверить по зачётке, благо я её не сдал после окончания учёбы.
JUnix ★
Вкратце: выложил на гитхаб свой старый проект «эмулятора» Линукса, который я писал
Мне тогда этот язык невероятно нравился, я был в бешеном восторге и такое вот несерьёзное к нему отношение меня очень задевало.
Эта фантазия меня так увлекла, что примерно год я посвящал ей все вечера, в ущерб учёбе в Университете и личной жизни. Позже я ещё долго возвращался к этому проекту, правда моя активность всё угасала, пока
Конечно настоящий эмулятор у меня не получился, да я и не пытался его создать, получилась имитация командной строки «Линукса» на том уровне знаний, который у меня был в те годы.
Реализованы
Поскольку шеллом
В своё время появление этого «эмулятора» понаделало шуму — о нём написали все основные российские сайты про программирование, а я каждый день получал письма от тех, кому понравилась моя игрушка.
Постепенно домен, на котором это всё располагалось, потерялся, а потом со старым винтом умер и бакап сайта. Осталась только копия
Так бы он там и лежал, но вчера в разговоре Леонидом Кагановым я случайно помянул почивший проект и Леонид, который его моментально вспомнил, предложил забрать сохранившееся
Честно говоря, я и сам давно об этом думал —
И вот вчера я наконец выкачал слепок первой попавшейся качалкой, поправил очевидные баги, чтобы всё заработало в современных браузерах — они
Кстати, если вам нравится в таком копаться, можете посмотреть сколько костылей в то время приходилось городить и как беден был раньше этот ДжаваСкрипт.
Я потестировал
Репозиторий проекта: https://github.com/bolknote/junix.
Сайт проекта, где можно посмотреть всё в действии: https://bolknote.github.io/junix.
JavaScript и иврит
Выше прекрасный способ сломать мозг стыком трёх языков — английского, иврита и ДжаваСкрипта.
Как многим известно, в семитских языках текст записывается справа налево. Когда я лет 20 назад участвовал в разработке сайтов для некой арабской страны, это был тот ещё вызов моему внутреннему набору аксиом. Прокрутка слева, выделение текста в другую сторону, клавиша Backspace стирающая в противоположную сторону от стрелки, которая на ней нарисована, и прочие прелести.
Тогда кажется только Эксплорер умел поддерживать такое и переключался он атрибутом dir целиком, всем окном. Сейчас у нас есть Юникод и специальные коды смены направления текста. В одной строке можно сколько угодно его менять.
В ДжаваСкрипте выше именно это и сделано — английское const написано в привычном нам направлении, а дальше направление текста изменено и буквам иврита значения присваиваются справа налево. Выглядит психоделически, конечно.
Дополнение: в комментариях меня поправили. Оказывается коды смены направления тут не нужны — программы автоматически меняют направление, основываясь на том символы какого языка используются, просто в этот момент захватываются ещё и небуквенные символы — пунктуация, цифры и так далее.
Банан велик, а кожура еще больше
Много
А тут братишка прислал в «Телеграм» кусок кода, который всплывает на «Стек Оверфлоу» не первый год:
console.log(0 >= null); // выведет true
console.log(0 > null || 0 == null); // выведет falseСтранное поведение, как ни крути, ведь даже название операции «больше или равно» раскладывается на две — либо «больше», либо «равно». Казалось бы оба варианта должны давать одинаковый результат.
Решил я полюбопытствовать есть ли объяснение этому феномену.
Во всех браузерах работает одинаково странно, а это значит, что
Let r be the result of performing abstract relational comparison lval < rval
If r is true or undefined, return false. Otherwise, return true
Что означает примерно следующее: если нам надо выполнить операцию
Что есть истина?
У нашего бывшего сотрудника в фейсбуке увидел ссылку на любопытную задачку — может ли (a == 1 && a == 2 && a == 3) быть «истиной» в ДжаваСкрипте?
Не заглядывая в статью, я предложил два варианта, вполне очевидный первый:
a = ((counter) => ({valueOf:() => ++counter}))(0)
// {valueOf: ƒ}
a == 1 && a == 2 && a == 3
// trueИ второй — на суррогатных парах. Но по ссылке есть ряд других занимательных способов — через геттеры, похожие символы Юникода (мне приходил в голову такой вариант, но я не смог подобрать три символа, одинаковых на вид), другие специальные методы и прочее. Больше всех мне понравился такой способ:
a = [1,2,3];
a.join = a.shift;
a == 1 && a == 2 && a == 3
// trueОчень просто и элегантно — массив при преобразовании в скаляр в ДжаваСкрипте, всегда сначала преобразуется в строку значених, объединённых через запятую. В коде выше метод для объединения заменяется методом, который забирает из массива первый элемент. И всё работает как требуется — при каждом сравнении массива с числом, забирается первый элемент массива.
Что-то не вижу — быть или не быть?
Слово «выглядит» в подписи ко скриншоту выше упомянуто неслучайно — программа написана так, что видимое — только надводная часть айсберга.
Уже в те далёкие времена, когда я только ещё начинал писать на ДжаваСкрипте, программисты на форумах задавались вопросом можно ли
Позже, когда производительность интерпретаторов ДжаваСкрипта стала стремительно повышаться, появились всяческие обсуфкаторы, сильно запутывающие программу за счёт удаления форматирования, внесения шума, замены имён переменных и функций и сжатия по словарю поверх всех замен.
С тех пор эти инструменты застыли примерно в том же состоянии, но теперь, мне кажется, в этом деле наметился прорыв.
Некто ФейкЮникод, разместил скрытое послание внутри суррогатных пар Юникода.
Что такое суррогатные пары расписывать не буду, статей на эту тему предостаточно, главное, что их не видно и они могу быть частью (кроме первого символа) любого идентификатора ДжаваСкрипта.
Я попробовал использовать способ с суррогатными парами, чтобы скрыть монолог Гамлета внутри программы на ДжеЭсФаке (способ написания программ на ДжаваСкрипте, когда не используются
Получилась адская обфускация, глядя на которую и не заподозришь, что она после запуска выведет в консоль полтора килобайта английского текста.
F★ckJS на поло
Очень в тему к предыдущему посту на работе нашёлся мешок с поло размера XXL
На поло программа на ДжаваСкрипте, которую я писал
«Странный» JS
В ДжаваСкрипте полно странностей, да, но популярная картинка, которая получила сейчас широкое хождение (не буду её приводить, просто суть перепишу) имеет отношение только к безграмотности её авторов. Суть такова:
[] + [] // массив + массив
"" // результат — пустая строка
[] + {} // массив + объект
"[object Object]" // В результате получаем объект? Ну ок
{} + [] // объект + массив
0 // 0? Реально?
{} + {} // объект + объект
NaN // Not a Number? WAT?Выглядит очень всё ооочень странно, если думать, что происходит именно то, что написано в коментариях справа. На самом деле происходящее неверно интерпретировано авторами.
В ДжаваСкрипте фигурные скобки, помимо объекта, задают ещё и блок кода. В данном случае все «объекты» слева — на самом деле блоки кода. Откроем консоль и попробуем это доказать:

Как видите, в левый блок можно без труда добавить код и станет очевидно, что это не описание объекта. Так два последних обсуждаемых примера распадаются на две независимые команды — определение пустого блока кода, который ничего не выполнит и унарную операция «плюс» над пустым объектом или массивом.
С массивом, возвращающим пустоту при суммировании (конкатенации) всё ещё проще — в данном случае производится попытка вызвать метод toString, который у массива выводит строку его значений через запятую. Так как значений нет, строка пустая.
В общем, ничего сильно странного не происходит.
Как скачать документ из АИС ЦГАСО-2
Самарский архив видимо
С просьбой
В конце работы в консоли будет длинная строка, начинающаяся с «dxo.itemsValue» и заканчивающаяся на «];». Её, как и в прошлый раз, надо будет скопировать в файл и передать в параметрах программе.
!function () {
var id = '[id$=seadragonContainer] div div';
var bt = '[id$=ForwardBtn]';
var pages = {};
var o = document.querySelector(id);
!function _observe(undefined) {
var ob = new MutationObserver(function() {
var imgs = $(id+' img[src*=Pages]:first');
if (imgs && imgs[0]) {
var page = imgs[0].src.match(/id=([^&]+)/)[1];
if (pages[page] == undefined) {
pages[page] = true;
ob.disconnect();
var next = $(bt);
if (next && next[0]) {
_observe();
next[0].click();
} else {
console.clear();
console.log("dxo.itemsValue=['" + Object.keys(pages).join("','")+"'];");
}
}
}
});
ob.observe(o, { childList: true, attributes: true, subtree: true });
}();
o.setAttribute('start', true);
}();Вообще, можно было бы заморочиться и сильно ускорить сбор данных, но я не буду этого делать — меня результат вполне устраивает.
Внимание: скрипт должным образом я ещё не протестировал (просто не успел), так что на корректную работу пока не рассчитывайте!