Курсы javascript
Обращаюсь к вам за советом.
Я сотворила небольшую библиотеку, состоящую из нескольких функций. Подключаю ее к страничкам в тегах .
Проверила их работу в браузерах: chrome, firefox, opera, ie, yandex.
Все работает (и даже на ie7 )
Дошло дело до проверки на мобильных устройствах. На моем телефоне (android 6.0.1) все работает. Решила проверить на более старых версиях: скачала эмулятор genymotion, создала в нем устройство андроид 4.1.
Споткнулась на первой же своей функции, даже вход в нее не происходит (проверила алертом).
Обычно, для отладки мне хватает консоли браузера. Но как ее включить на эмуляторе?
В инете вычитала, что можно протестировать в eclipse или в Phonegap. Но это же программы для мобильных приложений? У меня просто страничка без всяких наворотов (html, css, js (jquery 1.9)).
Посоветуйте, пожалуйста, как правильно отлаживать javascript на мобильных устройствах. Помимо андройда, меня ждет еще проверка на ios и на windows-phone
Буду рада любым советам!
Отладка Javascript на мобильных устройствах
Как я уже недавно писал, в настоящее время я занимаюсь разработкой мобильной версией одного сервиса. Вчерашняя статья про особенности дизайна сайта для мобильных устройств показала, что у аудитории есть интерес к разработке сайтов, адаптированных под телефоны, коммуникаторы и т.д.
Одним из важных моментов на сайте является динамика, за которую отвечает яваскрипт. К сожалению, полноценной поддержки от мобильных браузеров ждать не приходиться (например, jQuery, Prototype, Ext JS и т.д. не предназначены для браузеров данной категории, хотя надо заметить, что есть версии фреймворков, которые адаптированы под мобильные браузеры на основе WebKit). Но без поддержки популярных библиотек обойтись можно легко, основной недостаток в мобильных браузерах — отсутствие наличия средств отладки яваскрипта (возможно, что на Android, iPhone и др. продвинутых девайсах подобные инструменты есть, но я ориентируюсь на Opera Mini и, к сожалению, IE Mobile).
Интерпретатор Javascript в IE Mobile достаточно капризный, каждый раз вставлять в места возможных ошибок алерты очень неудобно. Коллега по работе подсказал использовать событие onerror:
window.onerror = function () alert( ‘An error has occurred!’ );
return true ;
>;
Затем мы пробовали добавлять аргументы, которые описывают ошибку: номер строки, адрес страницы… Однако, IE Mobile на ошибки все равно не реагировал. Поиск в интернете показал о существовании способа включения режима отладки для IE Mobile путем добавления параметра в реестр Windows Mobile:
Change the Registry setting “HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main” and Create a new DWORD key under Main with the name “ShowScriptErrors“. Set dword value of 1
К сожалению и данный способ также не дал никаких результатов.
В пятницу, когда мне пришлось довольно долго разбираться, где IE спотыкается, я решил обратиться к замечательной книге Д. Флэнагана «JavaScript. Подробное руководство» (вспомнил, что там была глава про отладку JavaScript. Предлагаемый способ совпадал с тем, что предлагали мне на работе, но я решил попробовать еще раз:
window.onerror = function (msg, url, line) alert(msg + «\n» + url + «\n» + «\n» + line);
return true ;
>;
И вот, данный вариант сработал! Ошибка была благополучно найдена и исправлена. Непонятно только, почему не помогло в первый раз, возможно, дело было в том, что данная функция сначала была расположена во внешнем js файле, второй раз я разместил ее непосредственно на странице — может быть именно это помогло IE ее использовать.
Надеюсь, что данный опыт кому-то пригодиться 🙂
- javascript debug
- mobile debug
- отладка javascript
Цифровой фотоаппарат DSC-RX0M2
Включение/выключение камеры со смартфона с помощью функции Bluetooth
Камеру можно включить или выключить с помощью смартфона, подключенного к камере посредством функции Bluetooth.
К смартфону можно одновременно подключить несколько камер. В отличие от множественных подключений посредством Wi-Fi все камеры подключаются к смартфону напрямую.
Поддерживаемые смартфоны
- Смартфоны с Android: Android 5.0 или более поздней версией, совместимые с Bluetooth 4.0 или более поздней версией*
- iPhone/iPad: iPhone 4S или более поздняя версия/iPad 3-го поколения или более поздняя версия
* Для получения последней информации см. сайт поддержки.
* Информация о версии Bluetooth приведена на веб-сайте для вашего смартфона.
Предварительная подготовка
Заранее выполните следующие действия.
- Установите Imaging Edge Mobile на смартфоне.
- Добавьте функцию [Дист. ВКЛ/ВЫКЛ питан. камеры] в Imaging Edge Mobile. Подключите смартфон к камере посредством Wi-Fi.
После подключения камеры посредством Wi-Fi функция [Дист. ВКЛ/ВЫКЛ питан. камеры] добавляется в Imaging Edge Mobile. Вам не нужно подключаться посредством Wi-Fi после первого раза.
Подключение Bluetooth (сопряжение)
Камеру и смартфон необходимо подключить посредством соединения Bluetooth (сопряжение). Эта процедура требуется только один раз.
- Убедитесь в том, что функция Bluetooth камеры включена.
- Выполнять сопряжение с использованием функции Bluetooth в данный момент не требуется.
- На камере выберите MENU → ( Сеть ) → [Настр-ки Bluetooth] → [Функция Bluetooth] → [Вкл] .
- Также убедитесь в том, что в камере [
Настр. пит. отдал.] установлено в [Вкл] .
- Также убедитесь в том, что в камере [
- На камере выберите MENU →
( Сеть ) → [Настр-ки Bluetooth] → [Связывание] .
- На мониторе камеры будет отображаться название устройства камеры “DSC-RX0M2”.
- Если соединение Bluetooth уже установлено, будет отображаться название модели подключенного смартфона.
- Запустите Imaging Edge Mobile на смартфоне и выберите [Дист. ВКЛ/ВЫКЛ питан. камеры] .
- Появится сообщение [Сканирование. ] и список устройств, которые можно подключить.
- Если [Дист. ВКЛ/ВЫКЛ питан. камеры] не появляется, выполните процедуру, описанную в разделе “Предварительная подготовка”.
- Выберите [Связывание] справа от “DSC-RX0M2” в списке на смартфоне.
- Выберите [Да] на экране подтверждения сопряжения на камеры.
- Выберите [Связывание] на экране подтверждения сопряжения на смартфоне.
- Выберите [Да] на экране завершения соединения Bluetooth на камере.
- Камера вернется к экрану [Настр-ки Bluetooth] .
Включение/выключение камеры со смартфона
После выполнения действий пункта 8 в разделе “Подключение Bluetooth (сопряжение)” камеру можно включать и выключать со смартфона с помощью описанных ниже действий.
- Запустите Imaging Edge Mobile на смартфоне и выберите [Дист. ВКЛ/ВЫКЛ питан. камеры] .
- Выберите
(Питание) справа от “DSC-RX0M2” в списке на смартфоне.
- Если камера выключена, она включится, а метка
(Питание) станет зеленой. - Если камера включена, она выключится, а метка
(Питание) станет белой.
- Если камера выключена, она включится, а метка
Совет
- Количество названий устройств, отображаемых на смартфоне, соответствует количеству камер, подключенных посредством функции Bluetooth. Выберите название устройства камеры, которую необходимо включить/выключить.
Примечание
- При инициализации камеры информация о сопряжении также будет удалена. Для повторного выполнения сопряжения удалите информацию о сопряжении со смартфона, а затем выполните процедуру, описанную в разделе “Подключение Bluetooth (сопряжение)”.
- Если соединение Bluetooth неустойчивое, удалите все препятствия, например людей или металлические предметы, между камерой и сопряженным смартфоном.
- Функция Bluetooth недоступна под водой.
- Если [Функция Bluetooth] установлено в [Вкл] , аккумулятор камеры постепенно разряжается, даже если камера выключена. Установите [Функция Bluetooth] в [Выкл] , если функция Bluetooth не используется.
- В зависимости от окружающих условий радиусы действия функции Bluetooth и функции Wi-Fi могут отличаться.
MediaDevices.getUserMedia()
Метод MediaDevices .getUserMedia() , при выполнении, вызывает всплывающий диалог, запрашивающий разрешение пользователя на использование медиа устройства (камера, микрофон). Результат возвращает промис, содержащий поток, который состоит из треков (дорожек), содержащих требуемые медиа типы. Этот поток может включать, к примеру, видеотрек, созданный либо аппаратным средством, либо виртуальным видеоисточником, такими как камера, устройство видеозаписи, сервис обмена изображениями и т.д); аудиотрек, созданный физическим или виртуальным аудиоисточником, к примеру, микрофоном, аналого-цифровым преобразователем звуков и возможно иные типы треков.
Он возвращает Promise , который, в случае согласия пользователя, разрешается MediaStream объектом. Если пользователь отказывает в разрешении, или медиа устройство не доступно, тогда промис отменяется с объектами типа NotAllowedError или NotFoundError соответственно.
Примечание: Возможно, что промис ни разрешиться, ни отмениться, в случае, когда пользователь не сделает выбор и запрос проигнорирует.
Обычно, разработчик получает доступ к единственному экземпляру объекта MediaDevices , используя navigator.mediaDevices.getUserMedia() (en-US) метод, возвращающий поток:
async function getMedia(constraints) let stream = null; try stream = await navigator.mediaDevices.getUserMedia(constraints); /* используем поток */ > catch (err) /* обработка ошибки */ > >
Тот же результат, но используя тип промиса :
.mediaDevices .getUserMedia(constraints) .then(function (stream) /* используем поток */ >) .catch(function (err) /* обработка ошибки */ >);
Примечание: Если документ загружен не безопасно, значение navigator.mediaDevices будет undefined , и нельзя будет использовать метод getUserMedia() . Смотри Security для дополнительной информации о дальнейших вопросах безопасности, связанной с использованием метода getUserMedia() .
Синтаксис
var promise = navigator.mediaDevices.getUserMedia(constraints);
Параметры
Объект MediaStreamConstraints (en-US), определяющий запрашиваемые медиатипы, вместе с требованиями для каждого типа.
Тип параметра constraints (ограничения) — это объект типа MediaStreamConstraints с двумя членами: video и audio , описывающие запрашиваемые медиатипы. Один или оба должны определяться разработчиком. Если браузер не сможет обнаружить медиатреки определяемого типа, которые соответствуют переданным ограничениям, возвращаемый методом промис отменяется с объектом NotFoundError .
Следующий отрывок кода запрашивает и audio и video типы без дополнительных условий:
audio: true, video: true >
Если определяется значение true для медиатипа, результирующий поток обязательно будет иметь в себе запрошенный медиатип. Если ни один из типов не включается в запрос, вызов метода getUserMedia() приведёт к ошибке.
Если полная информация о камерах и микрофонах пользователя недоступна по причинам конфиденциальности, приложение может запросить доступ только к необходимым ему функциям, используя дополнительные условия. Следующий пример запрашивает видеотрек с разрешением камеры 1280×720 пикселей:
audio: true, video: width: 1280, height: 720 > >
Браузер попытается выполнить условие, но может вернуть видеотрек другого разрешения, если установленные требования невозможно удовлетворить (камера не обладает возможностью такого разрешения), или пользователь переопределяет условие.
Для минимального, максимального и точного определения значения можно использовать ключевые слова min , max , или exact . Следующий пример запрашивает минимальное разрешение камеры 1280×720:
audio: true, video: width: min: 1280 >, height: min: 720 > > >
Если камера не в состоянии обеспечить указанное минимальное разрешение или более высокое, возвращаемый промис будет отменён с объектом OverconstrainedError , и пользователь не увидит диалога запроса разрешения.
Различие поведения происходит по причине того, что ключевые слова min , max , и exact являются обязательными к выполнению. В то время как простые значения и ключевое слово ideal — не обязательные к выполнению. Ниже, полный пример:
audio: true, video: width: min: 1024, ideal: 1280, max: 1920 >, height: min: 776, ideal: 720, max: 1080 > > >
Значение свойства ideal имеет приоритет. Это означает, что браузер будет пытаться обнаружить настройки с наименьшей fitness distance (удовлетворяющей разницей) от переданных идеальных значений. Т.е. пытается найти самое близкое к переданному значение.
Простые значения работают как идеальные, поэтому один из первых примеров выше можно переписать, используя свойство ideal :
audio: true, video: width: ideal: 1280 >, height: ideal: 720 > > >
Не все значения ограничений являются числовыми. К примеру, на мобильных устройствах следующий отрывок будет запрашивать переднюю камеру (если присутствует) , поэтому в свойстве facingMode указано строковое значение user :
audio: true, video: facingMode: "user" > >
Запрос обратной (задней) камеры:
audio: true, video: facingMode: exact: "environment" > > >
Следующее строковое свойство — deviceId (идентификатор устройства). Его значение может быть получено из метода mediaDevices.enumerateDevices() , возвращающего список, имеющихся на машине устройств, с их идентификаторами, и может быть использовано для запроса определённого устройства по идентификатору этого устройства:
video: deviceId: идентификаторНужнойКамеры; > >
Код выше вернёт запрашиваемую камеру или другую камеру, если требуемая камера недоступна. Для получения доступа к потоку только определённой камеры, без альтернативы, используется свойство exact (точно) :
video: deviceId: exact: идентификаторНужнойКамеры; > > >
Возвращаемое значение
Промис Promise , при удачном запросе требуемого медиа устройства разрешается объектом типа MediaStream .
Исключения (Ошибки)
При неудачном запросе медиа устройства, возвращаемый промис Promise отменяется объектом типа ошибки DOMException (en-US). Возможными ошибками могут быть типы:
Хотя пользователь и операционная система предоставили доступ к аппаратному устройству, и не возникло проблем с оборудованием, которые могли бы вызвать NotReadableError , возникла некоторая проблема, которая не позволила использовать устройство.
Возникает если, одно или несколько запрашиваемых устройств не может быть использованы в настоящее время. Это происходит тогда, когда контекст браузера является не безопасным (страница была загружена используя протокол HTTP вместо HTTPS), а также, если пользователь не разрешил доступ текущему экземпляру браузера к устройству, пользователь отказал в доступе в текущей сессии, или пользователь отказал в доступе к медиаустройствам глобально. Для браузеров, которые поддерживают управление медиаразрешениями с помощью Feature Policy, такая ошибка возвращается если Feature Policy не сконфигурирована для разрешение доступа к медиаустройству или устройствам
Примечание: Более старые версии спецификации использовали вместо этого SecurityError. SecurityError имеет новое значение.
Возникает если, типы медиа треков, удовлетворяющие переданным значениям, не найдены.
Хотя пользователь и предоставил разрешение на использование соответствующих устройств, произошла аппаратная ошибка на уровне операционной системы, браузера или веб-страницы, которая препятствовала доступу к устройству..
Возникает если, в результате указанных ограничений не было найдено устройств, отвечающих запрошенным критериям. Ошибка является объектом типа OverconstrainedError и имеет свойство constraint , строковое значение которого является именем ограничения, которое было невозможно встретить, и свойство message , содержащее читаемую человеком строку, объясняющую проблему.
Примечание: Ошибка может возникнуть даже, если пользователь ещё не выдал разрешение на использование устройства, использующиеся как поверхность для идентификации отпечатка пальца.
Возникает если, медиа поддержка отключена в Document на котором был вызван метод getUserMedia() . Механизм по которому медиа поддержка включается и отключается находиться в компетенции браузера пользователя.
Возникает если, список ограничений пустой или все ограничения установлены в false . Так же это происходит, если пытаться вызвать метод getUserMedia() в небезопасном контексте, поскольку в нем navigator.mediaDevices равно undefined .
Конфиденциальность и безопасность
Поскольку API могут существенно затрагивать вопросы конфиденциальности, спецификация getUserMedia() предъявляет широкий спектр требований защиты конфиденциальности и безопасности, которым современные браузеры обязаны следовать.
getUserMedia() — это мощная функция, которая может быть использована только в безопасном контексте (en-US) . В небезопасном контексте, navigator.mediaDevices равно undefined , предотвращая доступ к методу getUserMedia() . Безопасный контекст — это, если кратко, страница, загружаемая по протоколу HTTPS или file:/// URL схеме, или страница, загружаемая из localhost .
В нем обязательно запрашивается пользовательское разрешение к доступу audio или video источникам. Только контекст документа верхнего уровня, проверенного источника может запросить доступ, используя метод getUserMedia() . Если контексту верхнего уровня явно не даётся разрешение для данного (en-US) используя Feature Policy, пользователю никогда не будет предложено выдать разрешение на использование устройств, пока пользователь самостоятельно не отменит запрет в настройках браузера.
Дополнительные подробности на тему требований и правил, и как они отражены в контексте выполняемого кода, о том, как браузеры управляют вопросами конфиденциальности и безопасности читайте далее.
Конфиденциальность пользователя
Поскольку, программный интерфейс затрагивает вопросы конфиденциальности и безопасности, в спецификации, для метода getUserMedia() содержатся особенные требования для управления уведомлениями и разрешениями пользователя. Сначала метод getUserMedia() должен всегда запрашивать пользовательское разрешение до начала сбора данных потоков камеры и микрофона. Браузер может использовать функциональность одного разрешения на домен, но в первый раз браузер обязательно должен получить разрешение пользователя.
Не менее важны правила уведомления. Браузеры обязательно отображают индикатор, который указывает на использование камеры или микрофона, выше или сверху любого, уже имеющегося индикатора аппаратного средства (если присутствует). Они также должны отображать индикатор того, что разрешение уже выдано для использования устройств, даже, если устройства не используются в настоящий момент.
К примеру, в Firefox, в панели адреса отображается красная пульсирующая иконка, указывающая на использование аппаратного средства. Серая иконка указывает на выданное разрешение, но не использование аппаратного средства в данный момент. Физический (явный) свет устройства указывает на текущее использование (активизацию) аппаратного средства. Если отключить камеру программно, свет активности камеры отключится, указывая на то, что она запись не производит, без отмены разрешение на использования, и включиться после запуска камеры в работу.
Безопасность
Существуют несколько способов управлением безопасностью и контролем в user agent. Для этого можно использовать метод getUserMedia() , который возвращает объекты ошибок, относящиеся к безопасности.
Примечание: Модель безопасности для метода getUserMedia() находиться в процессе разработки. Первоначально спроектированный механизм безопасности находиться в процессе замещения Feature Policy, поэтому различные браузеры имеют разный уровень поддержки безопасности, используют различные механизмы. Вам необходимо осторожно тестировать свой код на различных устройствах и браузерах, чтобы удостовериться в его уверенной работоспособности.
Feature Policy (Функциональная политика)
Функция управление безопасностью (Feature Policy) протокола HTTP находиться в процессе введения в браузеры, с поддержкой, доступной в различной степени во многих браузерах (но не всегда включённой в настройках по умолчанию, как в Firefox). Метод getUserMedia() — один из методов, требующий использования функциональной политики и вашему коду нужно быть готовым к работе с ним. К примеру, чтобы метод был доступен в документах не высокого уровня, разработчику нужно использовать либо атрибут allow (en-US) на элементе (en-US) , который использует getUserMedia() , либо Feature-Policy (en-US) заголовок для страниц , передающихся с сервера, которые используют getUserMedia() .
Два разрешения, которые обращаются к getUserMedia() — camera и microphone .
К примеру, строка HTTP заголовка позволит использовать камеру документу и любым встроенные (en-US) элементам, которые загружаются из одного источника:
Feature-Policy: camera 'self'
Строка ниже, запрашивает доступ к микрофону для текущего источника и указанном в заголовке источнике https://developer.mozilla.org :
Feature-Policy: microphone 'self' https://developer.mozilla.org
Если используется getUserMedia() внутри элемента , можно запросить разрешение только для этого фрейма, которое явно более безопаснее, чем запрашивать более общее разрешение. Здесь нам нужно использовать и камеру и микрофон:
iframe src="https://mycode.example.net/etc" allow="camera;microphone"> iframe>
Прочитайте наше руководство Применение функциональной политики, изучив подробнее то, как это работает.
Безопасность на основе шифрования
Метод getUserMedia() доступен только для безопасных контекстов (en-US) . Безопасный контекст — это уверенность браузера в том, что документ был загружен безопасно, используя HTTPS/TLS, и имеет ограниченную подверженность небезопасным контекстам. Если документ не загружен в безопасном контексте, свойство navigator.mediaDevices равно undefined , делая невозможным доступ к методу getUserMedia() . Попытка получить доступ в такой ситуации приведёт к ошибке TypeError .
Безопасность источника документа
Существуют несколько небезопасных способа загрузить документ, который может попытаться вызвать метод getUserMedia() . Ниже представлены примеры ситуаций, в которых getUserMedia() не разрешается вызывать:
- Документ, загруженный в песочницу (en-US) элемента не может вызвать getUserMedia() , до тех пор пока, на элементе находиться атрибут sandbox (en-US), установленный в значение allow-same-origin .
- Документ, загруженный по протоколам data:// или blob:// в URL-адресе, не имеющий источника (такими являются типы URL-ов, введённые пользователями в строке адреса браузера) не может вызвать getUserMedia() . Подобные типы URL-ов, загружаемые из JavaScript-кода, наследуют разрешения скрипта.
- Иные ситуации, документы которых не имеют источника, к примеру элемент, содержащий атрибут srcdoc (en-US), использующийся для указания содержимого фрейма.
Примеры
Ширина и высота
Этот пример выбирает указанное разрешение камеры и присваивает ссылку на объект MediaStream свойству srcObject элемента video .
// Выбирает разрешение камеры близкое к 1280x720. var constraints = audio: true, video: width: 1280, height: 720 > >; navigator.mediaDevices .getUserMedia(constraints) .then(function (mediaStream) var video = document.querySelector("video"); video.srcObject = mediaStream; video.onloadedmetadata = function (e) video.play(); >; >) .catch(function (err) console.log(err.name + ": " + err.message); >); // always check for errors at the end.
Использование новых API в старых браузерах
Ниже, находятся примеры, использующие navigator.mediaDevices.getUserMedia() , с полифилами для работы в старых браузерах. Обратите внимание, что эти полифилы не корректируют все различия в синтаксисе, и не работают во всех браузерах. Рекомендуется использовать библиотеку adapter.js , как производственный полифил.
// Старые браузеры могут не реализовывать свойство mediaDevices, // поэтому сначала присваиваем свойству ссылку на пустой объект if (navigator.mediaDevices === undefined) navigator.mediaDevices = >; > // Некоторые браузеры частично реализуют свойство mediaDevices, поэтому //мы не можем присвоить ссылку на объект свойству getUserMedia, поскольку //это переопределит существующие свойства. Здесь, просто добавим свойство //getUserMedia , если оно отсутствует. if (navigator.mediaDevices.getUserMedia === undefined) navigator.mediaDevices.getUserMedia = function (constraints) // Сначала, если доступно, получим устаревшее getUserMedia var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; //Некоторые браузеры не реализуют его, тогда вернём отменённый промис // с ошибкой для поддержания последовательности интерфейса if (!getUserMedia) return Promise.reject( new Error("getUserMedia is not implemented in this browser"), ); > // Иначе, обернём промисом устаревший navigator.getUserMedia return new Promise(function (resolve, reject) getUserMedia.call(navigator, constraints, resolve, reject); >); >; > navigator.mediaDevices .getUserMedia( audio: true, video: true >) .then(function (stream) var video = document.querySelector("video"); // Устаревшие браузеры могут не иметь свойство srcObject if ("srcObject" in video) video.srcObject = stream; > else // Не используем в новых браузерах video.src = window.URL.createObjectURL(stream); > video.onloadedmetadata = function (e) video.play(); >; >) .catch(function (err) console.log(err.name + ": " + err.message); >);
Частота кадров
В некоторых случаях может быть желательна более низкая частота кадров, например, при передаче WebRTC с ограничениями полосы пропускания
var constraints = video: frameRate: ideal: 10, max: 15 > > >;
Передняя и задняя камеры
На мобильных устройствах.
var front = false; document.getElementById("flip-button").onclick = function () front = !front; >; var constraints = video: facingMode: front ? "user" : "environment" > >;
Спецификации
| Specification |
|---|
| Media Capture and Streams # dom-mediadevices-getusermedia |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
- Предыдущее navigator.getUserMedia() API.
- mediaDevices.enumerateDevices() : Перечисление доступных медиа устройств
- WebRTC API
- Media Capture and Streams API (Media Streams) (en-US)
- Screen Capture API (en-US): Захват области экрана как MediaStream
- mediaDevices.getDisplayMedia() (en-US): Получение потока, содержащего экранную область
- Taking webcam photos: Руководство по использованию getUserMedia() для получения снимком вместо видео.
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 3 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
