JSInterface – это компонент, который позволяет получить доступ к JavaScript объектам из среды браузера, прямо во Flash.
Далее под «JavaScript» средой я буду иметь ввиду браузер и выполняемый в нём JavaScript код с его объектами, методами и инфраструктурой. А под “Flash” я буду иметь ввиду Flash player встроенный в HTML страничку, исполняющий код из SWF файла с его объектами, методами и инфраструктурой.
Если говорить проще, то у нас есть два разных мира, один внутри Flash Player’а, а другой – снаружи. Одним из не многих средств коммуникации между ними является класс ExternalInterface внутри Flash Player'а. Я использовал этот способ для создания удобного API позволяющего создавать объекты, оперировать объектами, вызывать и создавать функции в JavaScript и т.д.
Главные особенности JSInterface:
- Передача JavaScript объектов и функций во Flash по ссылке.
- Создание JavaScript объектов во Flash.
- Одноранговый доступ к JavaScript объектам.
- Использование максимально приближённого синтаксиса к привычному.
- Переадресация JavaScript ошибок исполнения во Flash с возможностью их обработки.
- Передача Flash функций в JavaScript.
- Отложеный вызов JavaScript функций с возвратом результата выполнения во Flash.
- Подгрузка JavaScript и CSS файлов в HTML документ прямо из Flash.
Чтоб понимать, как пользоваться JSInterface, надо хотя бы в общем, понимать, как он работает. Я, насколько умею, изобразил связь между Flash и JavaScript на этом рисунке:

С обеих сторон есть свои стеки ссылок, которые необходимы для связи реального объекта и его оболочки, через которую осуществляется работа с этим объектом с другой стороны. К примеру, если передавать объект из JavaScript во Flash, то сам объект на самом деле передаваться не будет – вы получите Flash объект-обёртку JSDynamic, который содержит всю необходимую информацию о реальном объекте, для взаимодействия с ним. И через эту обёртку вы можете делать с объектом всё тоже, что и с обычными объектами: получать, удалять и применять значения свойствам, вызывать методы. Функция, переходя из JavaScript во Flash, проходит такой же путь, но есть большая разница между выполнением функции и получением ссылки на функцию. В первом случае, в JavaScript передаётся ссылка на объект и имя метода и там уже происходит вызов функции. А во втором случае, функция передаётся во Flash как любой другой объект - через стек ссылок, но на стороне Flash вы получаете не объект-обёртку, а функцию-обёртку (которая, кстати, тоже является объектом). Простые типы данных, такие как строки, числа, булевы значения и значения типа «void»(null, undefined) передаются напрямую во Flash без всяких ссылок и подарочных вариантов.
Для Flash ситуация обстоит немного иначе – Flash объекты передаются в JavaScript как простые данные(XML передаётся как строка) проходя по пути «родной» механизм пастеризации (преобразование в простой объект и будут переданы только перечисляемые свойства). Для того, чтоб передать объект в JavaScript, вам нужно преобразовать его в обычный объект и в него импортировать все необходимые свойства. Функции из Flash передаются, как и из JavaScript – по ссылке и оборачиваются функцией-обёрткой.
Следует сказать, что если вы перевели объект из JavaScript, а потом опять его возвращаете в JavaScript, то он будет обрабатываться по особенному – будет передана информация об оригинальном объекте, а потом произойдёт замена на оригинальный объект.
При использовании объектов JavaScript обращатся к его свойствам и методам следует, как к свойствам и методам полученного во Flash объекта. Вы можете получать значения свойств, удалять свойства и создавать в объектах или вызывать их, если содержимым свойства будет функция. Вы можете даже перебирать свойства с помощью for…in или for…each циклов.
Каждое обращение к свойствам и методам объектов влечёт за собой вызов метода ExternalInterface.call() с определёнными инструкциями и получение от него результата. За счёт этого и создаётся «живое» общение с JavaScript объектами из Flash среды.
Синтаксис
Чтоб дать понять, с чем вы будете иметь дело, я решил начать с примеров.
- Создание объекта:
var object:JSDynamic = new JSDynamic('Object');
Объект создаётся с помощью класса оболочки – JSDynamic. В качестве аргументов ему передаются имя класса (функции) создаваемого объекта и массив параметров, если они есть. Второй аргумент принимает любой тип, на случай если вы передаёте один параметр – его не нужно оборачивать в массив.
- Получение ссылки на любой объект в JavaScript:
var navigator:String = JSInterface.getInstance('navigator.appName') as String; trace(navigator); // Opera var body:JSDynamic = JSInterface.getInstance('document.getElementsByTagName("body")[0]') as JSDynamic; trace(body.nodeName); // BODY
JSInterface.getInstance() вернёт результат любого выражения. Если это простое значение ,то вернёт его, но без обёртки в JSDynamic.
Перебор свойств JavaScript объекта:
var nav:JSDynamic = JSInterface.navigator; for(var p:String in nav){ trace(p+' = '+nav[p]); /* получаем: appName = Netscape appCodeName = Mozilla appVersion = 5.0 (Windows; ru) language = ru mimeTypes = [object MimeTypeArray] ..... */ }
Перебор свойств и использование подобных циклов происходит в обычном режиме.
- Выполнение функции:
var doc:JSDynamic = JSInterface.document; var element:JSDynamic = doc.createElement('div'); trace(element.outerHTML); // ...<DIV></DIV>
Функции выполняются по тем же правилам.
- Отложенное выполнение функции:
var func:Function = function(str:String){ trace(str); } JSInterface.callLater(JSInterface.window, 'toString', null, func, 3000);
Работает как обычный таймаут, но если задать функцию обратного вызова(callback), то вернёт в него резульат выполнения функции.
- Применение Flash метода, как callback функции:
var body:JSDynamic = JSInterface.document.getElementsByTagName('body')[0]; var frame:JSDynamic = JSInterface.document.createElement("iframe"); frame.width = 300; frame.height = 300; if(JSInterface.navigator.appName.indexOf('Microsoft')>=0){ frame.onreadystatechange = function(){trace(frame.readyState);}; }else{ frame.onload = function(){trace('iFrame loaded');}; } frame.src = 'http://actualwave.com'; body.appendChild(frame);
Callback функции будут выполнены, как обычные JavaScript функции. Объекту, которому присвоена такая функция не обязательно знать о том, какая именно это функция.
Если вы заметили, я не вызываю каждый раз объект document через JSInterface.getInstance(). Класс JSInterface содержит в качестве свойств самые основные JavaScript объекты:
- window
- document
- navigator
- event
Так удобнее и при каждом вызове не создаётся новый экземпляр объекта JSDynamic, а используется уже созданный.
Интерфейс
Почти все методы и свойства необходимые для работы с JavaScript собраны в классе JSInterface.
Свойства:
- available:Boolean – Указывает на возможность или невозможность использования JSInterface.
- initialized:Boolean – Произведён ли процесс инициализации. Инициализация - обязательный процесс перед выполнением, каких либо действий с JSInterface.
- window:JSDynamic – Ссылка на JavaScript объект window.
- document:JSDynamic – Ссылка на JavaScript объект document.
- navigator:JSDynamic – Ссылка на JavaScript объект navigator.
- main:JSDynamic – Ссылка на JavaScript объект соответствующий тегу OBJECT/EMBED, который объявил текущий экземпляр Flash Player.
- event:JSDynamic – Ссылка на JavaScript объект event.
- useExceptionHandling:Boolean – Указывает на необходимость отлавливать JavaScript ошибки и отправлять во Flash.
- traceExceptionOnly:Boolean – Если включен режим перенаправления ошибок, то они будут выводиться только c помошью команды trace().
- exceptionHandler:Function – Если задана функция, то все ошибки будут «молча» перенаправляться в эту функцию. Она должна принимать один параметр типа Error или JSError.
Методы:
- initialize(arg:*=null):void – Метод инициализации, должен быть запущен до любых манипуляций с JavaScript. Принимает в качестве параметра любой тип объекта из которого можно получить путь, по которому был загружен этот SWF файл – DisplayObject, LoaderInfo, String. Этот аргумент можно не задавать, если вы точно знаете, что ваш SWF файл имеет ID/NAME в HTML структуре.
- getTitle(str:String):void – Получить заголовок страницы.
- setTitle(str:String):void – Задать заголовок страницы.
- getStatus():String – Получить статус окна.
- setStatus(str:String):void – Задать статус окна.
- getDefaultStatus():String – Получить статус окна, по умолчанию.
- setDefaultStatus(str:String):void – Задать статус окна, по умолчанию.
- getLocation():String – Получить путь по которому была загружена страничка(URL).
- getTopLocation():String – Получить путь по которому была загружена страничка в корневом фрейме.
- getLocationHash():String – Получить хеш из URL странички.
- setLocationHash(str:String):void – Задать хеш из URL странички.
- getCookieString():String – Получить строку с куки странички.
- setCookie(name:*, value:*, date:Date=null, path:String='/', domain:String='', secure:Boolean=false):void – Задать куки.
- getCookie(name:String):String – Получить куки по имени.
- removeCookie(name:*, path:String='/', domain:String='', secure:Boolean=false):void – Удалить куки по имени.
- clear(removeCallbacks:Boolean=true):void – Метод отчищающий стеки объектов переданных по ссылкам и в JavaScript и во Flash. Аргумент указывает на необходимость отчистки стека функций обратного вызова.
- getInstance(path:String):Object – Получить результат выполнения выражения в JavaScript.
- loadJavaScript(url:String, func:Function=null, type:String=''):JSDynamic – Загрузить JavaScript из внешнего источника. Аргумены: путь к файлу; функция обратного вызова выполняющаяся по завершению загрузки; значение атрибута type создаваемого HTML элемента. Возвращает ссылку на созданный HTML элемент.
- loadCSS(url:String, func:Function=null, type:String=''):JSDynamic – Загрузить CSS из внешнего источника. Аргумены: путь к файлу; функция обратного вызова выполняющаяся по завершению загрузки; значение атрибута type создаваемого HTML элемента. Возвращает ссылку на созданный HTML элемент. Следует обратить ваше внимание на то, что FireFox не вызывает onload по завершению загрузки стилей.
- callLater(obj:*, propName:String, args:Array=null, func:Function=null, timeout:uint=1):void – Отложеный вызов JavaScript функции. Аргументы: объект содержащий функцию; имя функции; массив её аргументов; функция обратного вызова, в которую будет передано возвращённое значение; значение таймаута, через которое будет вызвана функция.
Класс JSDynamic динамический, но имеет скрытое свойство «properties», находящееся в пространстве имен aw.external.js_interface. Оно возвращает список всех доступных, перечисляемых в объекте свойств. Так же, в этом пространстве имён лежат методы:
- forEach(callbackFunction:Function, useList:Boolean=false):void - Вызывает переданный метод для каждого свойства с аргументами: ссылка на текущий объект; имя свойства; значение свойства. Второй аргумент этого метода указывает на использование списка свойств вместо «живого» перемещения по объекту.
- remove():void – Удаляет ссылку на данный JavaScript объект из стека.
- isExists():Boolean – Проверяет наличие ссылки на данный JavaScript объект в стеке.
Функция, полученная из JavaScript, как и JSDynamic, имеет дополнительные методы. Но чтоб их выполнить, необходимо её привести к типу Object:
var func:Function = JSInterface.document.createElement; trace((func as Object).isExists()); (func as Object).remove();
Ещё полезно будет знать об объектах JSError, которые являют собой прообраз ошибки произошедшей в JavaScript.
Свойства:
- description:String – Описание ошибки. Если его нет, значит всё уместилось в стандартном параметре «message».
- fileName:String – Имя файла, в скрипте которого произошла ошибка.
- lineNumber:Number – Номер строки, на которой произошла ошибка.
- jsErrorName:String – Имя класса JavaScript ошибки.
- number:Number – Дублирует Error.id.
- targetObject:Object – Ссылка на объект в результате работы над которым произошла ошибка.
- targetParameter:String – Имя параметра, при обработке которого произошла ошибка.
Методы:
- getStackTrace():String – возвращает строковое представление стека функций, в процессе выполнения которых произошёл сбой.
Файлы
Примеры(как проект для Flex 3)
Для примеров необходимо изменить пути к bedug версиям HTML файлов с локальных на удалённые, я тестировал через localhost. Это можно сделать войдя в свойства проекта, а далее Run/Debug Settings -> Выбираете файл примера -> Edit... -> URL or path to launch, снимаете галочку с Use defaults и ставите удалённый путь на "Debug". Следует сказать, что пример с открытием окна браузера не корректно работает в Internet Explorer, т.к. он не даёт доступа к объекту документа дочернего узла.
Гигиена
Стеки, которые хранят всю информацию о «портированых» объектах сами не отчищаются. Вам нужно учитывать, что даже если вы перестали работать с JavaScript объектом и даже если его JSDynamic оболочку удалил GC, то это не значит что и JavaScript объект будет удалён из стека. Ссылки удаляются только если явно вызвать необходимые методы. Но если вы несколько раз обращаетесь к одному и тому, же JavaScript объекту, для него будет использована одна и та же ссылка – он не будет несколько раз в стеке. Для их отчистки стеков у вас есть инструментарий:
- Методы одиночного действия – у объектов JSDynamic и функций есть методы для удаления из стека и наявности ссылки на объект в стеке.
- Метод JSInterface.clear() – метод глобального действия, отчищает стеки полностью.
Использование SWF в качестве странички сайта – без HTML обёртки
Собственно, это была единственная, преследуемая цель при создании JSInterface, но потом я потерпел неудачу и решил изменить ситуацию к лучшему. Исследования показали, что проблемы в этом направлении имеет только Internet Explorer, любой версии. Суть проблемы заключается в том, что все браузеры при загрузке SWF файла, как Web странички воссоздают стандартное HTML окружение Flash player’а с тегами OBJECT и/или EMBED. Internet Explorer делает то же самое, но при этом, он явно загружает необходимый HTML код из DLL файла, по протоколу res://, при этом явно указывая источник загрузки. Получается, что Flash player и HTML документ находятся в разных правовых зонах и Flash player не имеет доступа к документу. Собственно, доступ к документу стал камнем преткновения потому, что без него (может, я ошибаюсь и по какой-то другой причине) Flash не только не может получить вызов функции обратного вызова, но и не может получить результат выполненной JavaScript функции.
Дальше я обнаружил, что после нажатия F5(Обновить страничку) и перегрузки документа доступ к нему из Flash player открывается (как оказалось, нажатие клавиши F5 достаточно мощное оружие при использовании IE). А потом мне подсказали, что есть славный метод reload(), выполняющий то же действие и оказалось, что он действительно решает проблему и после перезагрузки с помощью window.location.reload(); документ становится доступным. Я не решился это встраивать в JSInterface, т.к., согласитесь, выглядела бы странно самопроизвольная перезагрузка flash сайта.
Так, что теоретически, вы можете проверять доступ к документу с помощью JSInterface.document и если Flash запущен в браузере Internet Explorer, то делать одну перезагрузку, либо использовать HTML обёртку.:)
Upd. 18.12.2008
Статья устарела, ввиду появления JSInterface 2.
Метки: ActionScript 3, API, ExternalInterface, Flash, Flex, JavaScript, JSInterface
Олег, ну нифига себе! Иными словами, та самая задача без-html-ного запуска флэшки в браузере решена?
Можно и так сказать. Имея полный доступ к JavaScript API можно много чего делать. Я только не придумал что ещё делать с IE. Пока думал сделать лоадер, который бы при первом входе, если браузер IE, обновлял бы страничку, а потом – загружал флешку с данными. Безумно, но должно работать.
Хм, очень интересная работа, спасибо, только:
Меня вот это смущает, я бы наверное сделал разные методы, чтобы возвращали разные типы данных (getJSDynamic и тд) постоянный принудительный кастинг не есть хорошо, имхо.
Круто!
Но один вопрос для непосвящённого : зачем нам (в каких ситуациях) необходимо подгружать флешку без html-обвёртки ? В чем плюсы и в чем минусы?
2 Nirth
Да, тут я недоглядел. Сейчас я пишу следующую версию и заменю «return type» с «Object» на «*»(любой тип), это даст возможность не кастовать самому выходное значение.
2 Evgeniy
Просто задолбал HTML.
Может пригодиться если создаёшь целый сайт на флеше, на 100% площади всего окна. Мне просто приятно наблюдать …/index.swf?param=value
Явный минус – вы не можете самостоятельно определить отсутствие установленного флеш плеера у пользователя.
Явных плюсов пока нет.
Фигасе… о_0
Это мегакруто. Молодец!!!!
2 Admin:
достаточно в обертке сказать:
body { margin: 0px; overflow:hidden }
html, body {width:100%; height: 100% }
и получается флеш на целую страничку
да, и самому флешевому объекту ширину и высоту по 100% задать
А ти allowScriptAcces i allowFullScreen тестив?
В старом Firefox (версии 1.0.х) была проблема с width=»100%» height=»100%»
Может быть сейчас уже исправили, но раньше флешка вместо того, чтобы занимать всё окно, наоборот скукоживалась до непотребных размеров (может быть даже до 100х100 пикселей).