• Mozilla makes me crazy

    What I’d like to share with you is my suffering from dealing with browsers. I’m an author of a Clojure library that automates browsers. Briefly, the library doesn’t depend on Selenium since it implements the Webdriver protocol with pure Clojure. It brings flexibility and freedom in the way you’d like to build your API.

    At the same time, everybody who is willing to tame several browsers at once is doomed to open the Pandora box. There is the official standard I thought, so definitely everybody will follow it. A naive laddie I was.

    I don’t know if there is some kind of curse or black magic, but sometimes developers cannot just implement the standard as it dictates the things should be done. For example, it says the server should accept a value field. Developers write code that takes text instead. When I see it, I have strange feeling of being completely stunned without any understanding of what’s going on here.

    But what exactly I wanted to discuss is how does Mozilla develop their software.

    Briefly, they break everything apart moving further. And I don’t know how to deal with it. Maybe they enjoy coding in Rust so much that their mission to ship good software has faded away.

    I know I’m not a Mozilla engineer and would never be capable of joining them, but still. As a user of their products, namely Geckodriver and Webdriver utilities, I consider myself being right when criticizing them.

    With each next release of Geckodriver, it behaves worth and worth. When I first started developing the library (a bit less then 1 year ago), it was 0.13 version available and it worked fine. I wrote a bunch of unit tests that all passed. Nowadays, the latest version is 0.19 and it just doesn’t work.

    Yes, you heard correct, it does not even respond to any API call:

    ~/Downloads> ./geckodriver-0.19.0
    1510326430666 geckodriver INFO geckodriver 0.19.0
    1510326430680 geckodriver INFO Listening on 127.0.0.1:4444
    
    curl -X POST -d '{"desiredCapabilities": {}}' "http://127.0.0.1:4444/session"
    

    Calling curl utility throws a long stack trace saying something about sandbox restrictions without any word on how to fix that or at least where to look for help.

    Here is a related issue that confirms I’m not the first one who faced it. It’s closed! “As I said, this isn’t a problem with geckodriver”, one of developers says. OK, but I’m still curious about why does the version 0.13 work fine whereas switching on 0.19 leads to failure? Proof:

    ~/Downloads> ./geckodriver-0.13.0 --version
    geckodriver 0.13.0
    
    ~/Downloads> ./geckodriver-0.13.0
    1510327215587 geckodriver INFO Listening on 127.0.0.1:4444
    
    curl -X POST -d '{"desiredCapabilities": {}}' "http://127.0.0.1:4444/session"
    >>> {"sessionId":"e744bbdd-1b3f-9249-827f-02204bbc81c8","value":{"acceptInsecureCerts":...
    

    Ok, let’s decrease the version a bit. I downloaded them moving backwards in time. Again, 0.18 still does not work. We need to go deeper. The previous one numbered with 0.17 starts well, but suddenly, I cannot fetch a new session with my library, it just returns nil value. That’s strange.

    Going down to 0.15. Don’t know why, but I cannot fill any input field, the API fails with a strange error saying I didn’t pass the “text” field. Hm, I clearly remember that the API accepts “value” field. That’s what the W3C standard says. Geckodriver 0.13 follows it, but not the 0.15 release! After fetching the official Mozilla repo and searching in its history for a while, I found this (truncated):

    diff -r 225d1faf513b -r b429bf0078c4 testing/webdriver/src/command.rs
    --- a/testing/webdriver/src/command.rs	Tue Mar 07 18:50:56 2017 +0000
    +++ b/testing/webdriver/src/command.rs	Wed Mar 15 13:54:19 2017 +0000
    @@ -752,7 +752,7 @@
    
     #[derive(PartialEq)]
     pub struct SendKeysParameters {
    -    pub value: Vec<char>
    +    pub text: String
     }
    
     impl Parameters for SendKeysParameters {
    @@ -760,26 +760,14 @@
             let data = try_opt!(body.as_object(),
                                 ErrorStatus::InvalidArgument,
    ...
    -            Ok(chars[0])
    -        }).collect::<Result<Vec<_>, _>>());
    +        let text = try_opt!(try_opt!(data.get("text"),
    ...
    +            text: text.into()
             })
         }
     }
    @@ -787,10 +775,7 @@
     impl ToJson for SendKeysParameters {
         fn to_json(&self) -> Json {
             let mut data = BTreeMap::new();
    -        let value_string: Vec<String> = self.value.iter().map(|x| {
    -            x.to_string()
    -        }).collect();
    -        data.insert("value".to_string(), value_string.to_json());
    +        data.insert("value".to_string(), self.text.to_json());
             Json::Object(data)
         }
     }
    
    

    Hold on, what was the purpose to change that field? Everything was working, right? Who asked for that? Did the standard change? No, it’s still the same! Why have you guys just changed the API? You could easily rewrite the code without renaming “value” field into “text”.

    Listen, I’ve already got three different versions of those API for Chrome, Firefox and Safari. In addition to that, your force me to switch on version inside Firefox branch turning my code into if/else hell.

    From your perspectives, what’s the difference in processing either “value” or “text” field? But for me, you’ve broken my software! It doesn’t work anymore.

    I could not find a diff that would proof the fact of moving “sessionId” field because the repo’s history is pretty complicated. But it’s easy to check that. The version 0.17 returns:

    {
      "sessionId":"4decec3e-e564-b349-bb41-b27b5f307e01",
      "value":{
        "acceptInsecureCerts":false,
        "browserName":"firefox",
        "browserVersion":"52.0
        ...
    

    whereas 0.13 returns:

    {"value":{
      "sessionId":"d0dc41f8-9c17-934e-be2b-708c72f0fb9c",
      "capabilities":{
        "acceptInsecureCerts":false,
        "browserName":"firefox",
        ...
    

    Look, they just wrapped the whole data into “value” subfield shifting it one level below. Again, what was the purpose of doing this? Did you think of those users who have already been using your code for some time?

    Thanks to those weird changes, I need to refactor my code just because Mozilla guys decided to rename something. And more over that, thank you for not sending back the driver’s version in API responses. It would be not so challenging when having it.

    I wonder why has Chromedriver been working all that time without errors? It’s written in C++ and Python and the code is quite simple and linear. And it’s much stable then its Rust-driven rival.

    OK, it’s time to summarize all together. I don’t think that anybody will share my misery on the subject. The Webdriver spec is quite specific thing, so who would really care…

    But the main point of that talk is you should never break backward capability even at the gunpoint. Just keep things working, that’s the main priority when you change something. Extend, but not truncate your product. And really, just use it sometimes in a way that an ordinary user does.

  • Последнее видео с Рефакторинга

    Клуб Рефакторинга живет и здравствует, хоть я и забываю репостить видео в блоге. Вот пачка последних выступлений:

    Ссылки на слайды в описании к каждому видео. Больше записей на нашем Ютуб-канале.

  • Silent software

    I really value such kind of software that does not interrupt you. It should have happened to you I believe: you open a notebook when thouthands of notification balloons appear above the lock screen. Icons are jumping, everything is blinking. Or you open a program that immediately shows daily tips, asks for updates and so forth. Once you’ve closed the last tooltip, you start recalling what were you about to do with that program.

    That was the reason I abandoned Firefox. Every its extension, when updated, opened a new tab with release notes. So every time I opened a browser I had to close those tabs I didn’t ask to open.

    I’ve been fighting with that as I can for a long time. I turn off all the notifications on both laptop and mobile phone. I cut unwilling elements with Adblock and dump those patters into private Git repo. If a website doesn’t have RSS feed I don’t use it. I read long texts only with Kindle.

    I cut YouTube interface completely to see only a search bar and the video player by itself. No recommended sidebar or comment feed.

    When I open my laptop willing to send a email but some program prevents me from that (no matter was is a notification or a sound) it’s a serious reason to stop using it. If any site sticks a city selection or email subscription dialog across the whole screen I close it. No worries, I’ll find another one even better.

    A rule of being not interrupted by software as important as a rule of being not interrupted by other people. Everybody has a right to be alone.

    I hope one day that simple thing will occur to those who develop software.

  • Ссылки на выходные #29

    Севодня выпуск про женщин. Гендерное равенство, все дела.

    Любите женщин!

  • Как шарить картинки через Гитхаб

    Илья жалуется на облачные хранилища за то, что не показывают нормально картинки. Нормально значит средствами браузера по прямой ссылке.

    Действительно, это проблема. Картинку, расшаренную через какой-нибудь условный Дропбокс, невозможно открыть без мата. Сначала прогрузится верхняя панель, потом тулбар, скрипты и виджеты, а в конце, может быть, дойдет до картинки.

    Еще хуже обстоят дела с документами. Интерфейс рисует их прямо на странице: медленно, с тормозами, огромным потреблением памяти. Нет глупее поступка, чем шарить файлы через облачные сервисы.

    Я пишу этот пост чтобы, во-первых объяснить причины такого поведения, а во-вторых, предложить достойное решение.

    Ни один облачный сервис, будь то Дропбокс, Гугл-диск, Яндекс-диск, Мейл-облако, не даст прямую ссылку на файл. Виноваты в этом не программисты и даже не менеджеры. Просто сервис с прямыми ссылками становится убыточным и рискует закрыться.

    Если дать пользователям анонимно ссылаться на файл в сети, это мгновенно превратит сервис в хостинг картинок для форумов и борд. А это противоречит бизнес-целям сервиса. Ведь он задумывался не как хостинг, а хранилище личных данных. Возникает разница между целью и результатом.

    Может, вы помните, в Дропбоксе была особая папка public, содержимое которой видно любому анониму. Пользователи шарили из нее картинки для блогов и соцсетей. Особо одаренные держали там статичные сайты. Какой-нибудь условный Вася постил с Дропбокса смешную фотку на Пикабу, и сервис раздавал ее сотни тысяч раз. Теперь представьте, что фотка не одна, а сто, и таких Вась тысячи.

    Дропбокс, как известно, хостится на Амазоне и выплачивает огромные деньги за трафик. При этом им нужно окупить расходы на инфраструктуру, офис и Гвидо Ван Россума лично. Поэтому минимизация трафика и запрет на прямые ссылки – неизбежное следствие.

    Конечно, открывая картинку в интерфейсе Дропбокса, я всецело на стороне пользователя. Но другой частью разума понимаю, что шаринг по прямой ссылке превратит сервис в файло-помойку.

    Внезапно, самый удобный способ пошарить картинку сегодня – это Гитхаб! Для этого не нужно пихать файл в репозиторий. В Гитхабе, кстати, не дураки сидят: все статичные медиа-файлы из репозитория они раздают с заголовком Content-Disposition: attachment, что говорит браузеру скачать файл, а не открыть для просмотра.

    Однако, если залить картинку в любой раздел проекта, например, ишью, пулл-реквест, вики или комментарий, она будет доступна по прямой ссылке без каких-либо проблем, анонимно и круглосуточно. Достаточно перетянуть файл в текстовое поле, картинка тут же подгрузится, в тексте появится ее уменьшенная копия со ссылкой на оригинал.

    Я иногда пользуюсь этим способом, если в тексте много фотографий. Блог у меня на Гитхабе, однако заливать в репозиторий много бинарных файлов нехорошо. Поэтому я создаю в проекте блога ишью с именем, например “Baltimore photos”, забрасываю туда фотки и копирую ссылки. Посмотреть в действии можно в моем рассказе про Балтимор. Обратите внимание на адреса картинок. А вот ссылка на тот самый ишью с фотками.

    Я не скажу точно, какие ограничения накладывает Гитхаб на такой хостинг. Скорее всего, система учитывает домены и считает трафик, но еще ни разу не сталкивался с ограничениями. Конечно, этот способ не подойдет для аудитории какого-нибудь Варламова. Но для блога и небольших сообществ – вполне.

    Наконец, Гитхаб – отличный способ организовать рабочий процесс. Не кидайтесь картинками через Телеграм. Заведите проект, в нем тасочку, добавьте метки, ответственных. Все файлы и комментарии должны оседать в таске, чтобы через год открыть и все вспомнить.

    Предположим, студенты должны прислать работы к конкурсу. Создайте проект в Гитхабе (приватный, если нужно). Каждый участник открывает таску со своим именем, подливает файл. Он доступен по прямой ссылке. Все комментируется, трекается, хранится неограниченно долго, на почту приходят письма.

    Мне кажется, проще и удобней сегодня ничего нет.

  • Егор Бугаенко в Глубоком Рефакторинге. Анонс

    В декабре в Воронеж приедет Егор Бугаенко – известный разработчик, евангелист, автор книг. Егор выступит в клубе Рефакторинга с двумя докладами на темы разработки, собеседования и карьерного роста. Встреча будет полностью посвящена визиту Егора.

    В перерывах и после мероприятия зрители смогут пообщаться с гостем в неформальной обстановке и задать вопросы. Будет трансляция, позже – видео на нашем канале и Егора с английскими субтитрами.

    Короче, это уникальная возможность повидаться с известным разработчиком. Если вы в Воронеже и как-то связаны с ай-ти, приходите обязательно и поделитесь новостью с друзьями.

    Тем, кто слышит о Егоре впервые, следует почитать его блог и посмотреть выступления на Ютубе.

    Поскольку гостя такого ранга мы принимаем в первый раз, коллеги запустили гугло-форму с целью прощупать почву. Прежде всего нас интересует число участников, чтобы не пришлось под Новый год носиться в поисках помещения.

    Напомню, чат Рефакторинга у нас в Телеграме, есть вопросы – ответим.

  • Elegant Objects

    book

    I’ve just got Yegor Bugayenko’s “Elegant Objects” book. Even without reading a single page of it, I can definitely say the book worths doing it. How do I know in advance you may ask?

    That’s simple: it’s got great design. You may prove it too when opening the book at somewhere in the middle of it and scan the page.

    How does an ordinary IT book look like? Usually, its design is full of details made without any sense. A typical O’Reilly book makes me feel like a kid: it has lots of different paragraph styles, borders, lines, gray bars. Before you start reading, there is a legend with up to five icons. Look, that icon means to be careful, this one stands for “experienced users only” and so forth. Every page carries the author’s name, the title of the book and the chapter caption as if I really need to keep all of that in my mind constantly.

    Instead, the Yegor’s book are made of high quality design. Every page has only text but nothing else. No lines, bars or icons. There only two text stiles per the entire book, one is for ordinary text and the second one is for code. Again, the code is not put into the colored bar or whatever. It’s just text, and this is amazing.

    Except the main text, there is no any information on a page but its number. I really appreciate such design because it servers the only thing it was aimed for – to encourage me to read the book (but not to buy and put it on my shelf).

  • Без ORM. Доклад в Глубоком Рефакторинге

    Выступил на последнем митапе с докладом про ORM. Редкий случай, когда самому понравилось. По этой причиной решил поделиться с вами.

    Слайды

    Планирую написать расшифровку на английском.

  • Съездил в Балтимор

    На прошлой неделе съездил в Балтимор на конференцию по Кложе. Впервые посетил США. Гулял по набережной, видел Рича Хикки – словом, путешествие удалось. Ниже – случайные заметки обо всем, что осталось в голове, плюс немного любительских фото с завеленным горизонтом.

    Визу в США я успел получить за неделю до событий, когда повыгоняли американских дипломатов, а штаты в ответ усложнили выдачу виз. Насколько я знаю, визу и сейчас можно получить, просто это займет больше времени. Документы лучше готовить в местном визовом центре, стоит примерно 10 тыс. рублей.

    В Москве при входе в посольство случился казус: внутрь не пускают с сумками и рюкзаками. А у меня ноут, документы, все дела. Ячеек нет, куда идти неясно. Рядом тусуются мутные типы, принимают вещи на хранение. Пришлось бегать по магазинам в поисках ячеек. Это полный пиздец, конечно. Вопиющее неуважение к людям.

    На собеседовании уделяют особое внимание людям пожилого возраста. Никто не хочет, чтобы приехал очередной нахлебник. Одного дедулю, собравшегося к сыну, взяли в оборот и мурыжили где-то час. Пока стоял в очереди, услышал его биографию от пятого колена, но так и не узнал, дали визу или нет.

    Тех, кто едет по бизнесу, просят шпрехнуть на английском. Одна пара утверждала, что едут заключать контракты на миллионы, а сами ни бельмеса по-английски. Тут не надо быть офицером, чтобы что-то заподозрить.

    Впереди стояла женщина, которая была в посольстве явно не в первый раз, всем указывала, все знала. На собеседовании сказала, что собралась замуж за американца. Это было так жалко. Брак считается очень слабой причиной для визы. Вас расспросят о самых интимных моментах знакомства, попросят личную переписку, фотки, открытки, потому что жалающих попасть в США через мнимую любовь хоть отбавляй.

    Когда летишь из провинции в провинцию, твой путь лежит через столицы, хочешь этого или нет. Из Воронежа в Москву, оттуда в Нью-Йорк, затем в Балтимор. Обратно сначала на авто в Вашингтон, оттуда в Германию, в Москву и в Воронеж. Шесть самолетов. 20 часов туда, 15 обратно.

    Самое тяжелое – перелетать океан. Занимает 9 часов, временной сдвиг, устаешь от самолета. Ближе к концу наступает апатия: и книжки надоели, на ноуте делать нечего, читалка внушает отвращение.

    Смутно помню, что все путешествие меня преследовал голод. Почему-то не удавалось нормально поесть. Во время пересадок я только успевал менять терминалы. Опасаясь шмона в США, выкинул кошерную воронежскую булочку (везде пишут, что еда строго запрещена). Во время очередного полета мне эта булочка приснилась.

    Посмотрел в самолете Сферу с Гермионой. (Только не поправляйте, я все равно не помню актеров по имени. Вы еще скажите как Гарри Поттера зовут.) Удивительно глупый фильм, я прямо удивился, как такую пургу сняли. Опоздали лет на 10-15. Героине тридцатник, а она решает проблемы подростков. “Чтобы добиться демократии, нужно заставить всех голосовать силой”. Лучше бы в Золушке снялась. Не рекомендую.

    На конференции видел Рича Хикки и даже сидел рядом с ним на соседнем стуле. Это дает преимущество в споре с любым кложуристом, верно? Под конец с ним можно было даже сфотаться, но я что-то тупанул. Зато стал свидетелем этих кадров:

    Кложе 10 лет, Рич режет юбилейный торт. Шок, смотреть до конца.

    Балтимор считается неблагополучным городом. Половина населения черные. Кварталы с неграми опасны, там берзаботица, гетто и обособленная от общества жизнь. Где белые, там более-менее хорошо. Балтимор один из городов-лидеров по обороту наркотиков.

    В центре города чисто, хорошая инфраструктура. Нет бордюров, пространство плавное. Ходят трамваи, машин мало. Светофоры и знаки расставлены правильно, переходами пользоваться удобно. Все парковки платные. Встречаются заборы, закругленные снизу. Интересно, зачем это? Чтобы ничего нельзя было прислонить? Для устойчивости?

    Интересно выглядят автомобили. Много длинных грузовичков. Тяжелая техника брутальна: трубы, хром, цепи до пола.

    В Америке паровое отопление, по вечерам то тут, то там из недр канализации выходит пар. Он с особым запахом, но ничего противного в этом нет. Наоборот, по мне это так романтично! Вспоминаю угрюмый Нью-Йорк из рассказов и компьютерных игр: ночь, улица, фонарь, полицейская сирена, пар из люка.

    В город глубоко вдается гавань. Это прекрасное место! Не застроено всяким говном как бывает в России, нет. Там целая инфраструктура: пристань, музей, памятник, большое пространство для прогулок и отдыха.

    С одной стороны гигантский книжный магазин на пять этажей. Это бывшая корабельная верфь. От пола до крыши восходят 5 огромных труб как у Титаника. Внутри балки и кирпичи, гайки размером с кулак, но все чисто и опрятно.

    Это даже не магазин, а этакий центр творчества. Прямо среди сталлажей сидит черная молодежь и что-то креативят: рисуют фломастерами, разгадывают кроссворды, плетут фенечки. Кто-то валяется на полу с книжкой, никому нет дела.

    Напротив магазина два в одном: океанариум и ботанический сад. Выглядит как огромный стеклянный куб, сквозь который все видно. Билет стоит 20 долларов, на входе фотографируют.

    Поразил водопад на 5 этажей, очень круто. Хотя воронежский океанариум в Сити-парке все же лучше, серьезно.

    В Америке много толстых людей. Это особенно заметно после России. У нас население хоть здоровьем не блещет, но, на мой взгляд, толстых меньше. Полные в основном люди пенсионного возраста. А в штатах много юных толстяков. Смотрится гротескно и неприятно. На молодом теле жир лежит искусственно, словно привязанная подушка.

    Толстяки не идут, а плывут, словно мыльные пузыри. Я бы на месте министра здравоохранения схватился бы за голову. Лишний вес, да еще в столь юном возрасте – это же какая нагрузка на сердце!

    Плохое питание главная причина полноты. В Америке едят черт знает что. Понятно, я не мог обойти магазины, но нигде не видел фруктов или нормальной молочной продукции. Сплошные бутерброды и пицца, все острое, соленое, с майонезом. Из напитков только кофе и газировки. Чай если есть, то почему-то холодный и сладкий.

    Американцы считают чай бзиком азиатов и держат пакетики на ресепшене только для китайцев. Это не обязательно будет настоящий чай, может оказаться аптечный сбор, ромашка, какое-то сено – один хер чай. В буфете на конференции я спер несколько пакетиков Липтона, был счастлив.

    Не мог не обратить внимание на то, как отличается американское произношение от британского. Я раньше в этом ничего не понимал, казалось бы, и там и тут английский. Ан нет. У американцев больше диалектов, произношений. В их восприятии британский английский слишком академичен. С удивлением заметил, что никто не понимает меня с первого раза. И лишь только когда разговоришься с человеком, налаживается диалог.

    Совершенно невозможно понять негров. Когда черный служащий обращался ко мне, я понимал только по отдельным словам. Послушал разговор негров на остановке – вообще ни слова не понял, набор звуков. Мэтт, учитель английского, объяснил в чем дело.

    Черные горазды на диалекты. Иной раз они столь сильно отличаются от оригинала, что им дают имя. В Балтиморе преобладает т.н. “Black English”, английский для черных. В нем начисто игнорируются некоторые основы языка, например глагол “be” не склоняется по лицам и временам: черные говорят “I be at home” (вместо “I am at home” или “I was at home”). Не считая того, что целые слоги проглатываются, ударения смещены и все в этом роде.

    Даже черные учителя борятся с этим, снижая за black English оценки ученикам. Кажется, не помогает.

    Словом, после того как я пожаловался Мэтту на трудности, он ответил, что это норма (малышева.jpeg). Сказал, что проблемы в восприятии британцев американцами и наоборот - старая проблема. Я теперь занят американским английским.

    Балтимор портовый город. Под вечер улицы наполняются молодыми чернокожими. Они улыбаются и просят немного мелочи. Город знаменит крабами, которых ловят в океане и продают за баснословные деньги. Правильно приготовленный краб считается здесь чем-то вроде утки по-Пекински в Китае. Ходят слухи, что туристам под видом дорогих крабов впаривают обычных, на порядок дешевле.

    Футболки на конференции были с логотипом Кложи и крабом. Я только потом догадался.

    Американцы гротескно вежливы. Если ловишь чей-то взгляд, обязательно кивнут, улыбнутся, хау-ю-ду. Это хорошая практика.

    Продираясь через бесконечные аэропорты, иной раз помогал женщинам с чемоданами. Все эти базары что европейские женщины отказываются от помощи – брехня. Ни одна не отказалась. Потому что там хоть и Европа, а переходы иной раз тоже через жопу. А вот как толпы негров идут на расслабоне мимо бабушки с чемоданом на лестнице, это для меня вопрос.

    До свидания, Америка! Хоть и тяжело добираться, было круто. Еще вернусь.

  • Dealing with emoji in Clojure

    Generally, I hate emoji and try to avoid them everywhere I could. Those colored faces look dull to me comparing to a good old text smile. But still, emoji might be helpful replacing icons with them. When you need a globe, a mail envelope or a flight sign, putting a proper emoji could be a fast and good enough solution.

    After long Python experience, I though Java supports long unicode literals started with capital U and two bytes as follows (Python version):

    >>> print len(u"\U0001F535") # prints 2
    

    Surprisingly, it doesn’t. But I needed to put a blue circle sign that’s got U+1F535 number. So how should I turn that number into a string?

    term

    After googling for a while, I’ve done with a short Clojure function:

    (defn unicode-to-string
      "Turns a hex unicode symbol into a string.
      Deals with such long numbers as 0x1F535 for example."
      [code]
      (-> code Character/toChars String.))
    

    Usage example:

    term

    Adding it into business logic:

    (let [caption "Some important feature"
          is-on? (get-feature-state)
          sign (if is-on?
                 (unicode-to-string 0x1F535)  ;; blue circle
                 (unicode-to-string 0x26AA))] ;; white circle
      (str sign \space caption))
    

    Depending on whether the feature was enabled or not, the result message will have either a blue (active) or white (inactive) circle in front of it.

Страница 1 из 35