JSInterface – JavaScript во Flash

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:
  1. var navigator:String = JSInterface.getInstance('navigator.appName') as String;
  2. trace(navigator); // Opera
  3. var body:JSDynamic = JSInterface.getInstance('document.getElementsByTagName("body")[0]') as JSDynamic;
  4. trace(body.nodeName); // BODY

JSInterface.getInstance() вернёт результат любого выражения. Если это простое значение ,то вернёт его, но без обёртки в JSDynamic.

Перебор свойств JavaScript объекта:

  1. var nav:JSDynamic = JSInterface.navigator;
  2. for(var p:String in nav){
  3. trace(p+' = '+nav[p]);
  4. /* получаем:
  5. appName = Netscape
  6. appCodeName = Mozilla
  7. appVersion = 5.0 (Windows; ru)
  8. language = ru
  9. mimeTypes = [object MimeTypeArray]
  10. .....
  11. */
  12. }

Перебор свойств и использование подобных циклов происходит в обычном режиме.

  • Выполнение функции:
  1. var doc:JSDynamic = JSInterface.document;
  2. var element:JSDynamic = doc.createElement('div');
  3. trace(element.outerHTML); // ...<DIV></DIV>

Функции выполняются по тем же правилам.

  • Отложенное выполнение функции:
  1. var func:Function = function(str:String){
  2. trace(str);
  3. }
  4. JSInterface.callLater(JSInterface.window, 'toString', null, func, 3000);

Работает как обычный таймаут, но если задать функцию обратного вызова(callback), то вернёт в него резульат выполнения функции.

  • Применение Flash метода, как callback функции:
  1. var body:JSDynamic = JSInterface.document.getElementsByTagName('body')[0];
  2. var frame:JSDynamic = JSInterface.document.createElement("iframe");
  3. frame.width = 300;
  4. frame.height = 300;
  5. if(JSInterface.navigator.appName.indexOf('Microsoft')>=0){
  6. frame.onreadystatechange = function(){trace(frame.readyState);};
  7. }else{
  8. frame.onload = function(){trace('iFrame loaded');};
  9. }
  10. frame.src = 'http://actualwave.com';
  11. 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:

  1. var func:Function = JSInterface.document.createElement;
  2. trace((func as Object).isExists());
  3. (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 – возвращает строковое представление стека функций, в процессе выполнения которых произошёл сбой.


Файлы

Компонент для Flash

Компонент для Flex

Примеры(как проект для Flex 3)

Для примеров необходимо изменить пути к bedug версиям HTML файлов с локальных на удалённые, я тестировал через localhost. Это можно сделать войдя в свойства проекта, а далее Run/Debug Settings -> Выбираете файл примера -> Edit... -> URL or path to launch, снимаете галочку с Use defaults и ставите удалённый путь на "Debug". Следует сказать, что пример с открытием окна браузера не корректно работает в Internet Explorer, т.к. он не даёт доступа к объекту документа дочернего узла.


Гигиена

Стеки, которые хранят всю информацию о «портированых» объектах сами не отчищаются. Вам нужно учитывать, что даже если вы перестали работать с JavaScript объектом и даже если его JSDynamic оболочку удалил GC, то это не значит что и JavaScript объект будет удалён из стека. Ссылки удаляются только если явно вызвать необходимые методы. Но если вы несколько раз обращаетесь к одному и тому, же JavaScript объекту, для него будет использована одна и та же ссылка – он не будет несколько раз в стеке. Для их отчистки стеков у вас есть инструментарий:

  1. Методы одиночного действия – у объектов JSDynamic и функций есть методы для удаления из стека и наявности ссылки на объект в стеке.
  2. Метод 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.

Метки: , , , , , ,

Комментарии (10) на «JSInterface – JavaScript во Flash»

  1. Олег, ну нифига себе! Иными словами, та самая задача без-html-ного запуска флэшки в браузере решена?

  2. admin:

    Можно и так сказать. Имея полный доступ к JavaScript API можно много чего делать. Я только не придумал что ещё делать с IE. Пока думал сделать лоадер, который бы при первом входе, если браузер IE, обновлял бы страничку, а потом – загружал флешку с данными. Безумно, но должно работать. :)

  3. Nirth:

    Хм, очень интересная работа, спасибо, только:

    var navigator:String = JSInterface.getInstance(‘navigator.appName’) as String;
    trace(navigator); // Opera
    var body:JSDynamic = JSInterface.getInstance(‘document.getElementsByTagName(«body»)[0]‘) as JSDynamic;

    Меня вот это смущает, я бы наверное сделал разные методы, чтобы возвращали разные типы данных (getJSDynamic и тд) постоянный принудительный кастинг не есть хорошо, имхо.

  4. Evgeniy:

    Круто! :)
    Но один вопрос для непосвящённого : зачем нам (в каких ситуациях) необходимо подгружать флешку без html-обвёртки ? В чем плюсы и в чем минусы?

  5. admin:

    2 Nirth
    Да, тут я недоглядел. Сейчас я пишу следующую версию и заменю «return type» с «Object» на «*»(любой тип), это даст возможность не кастовать самому выходное значение.

    2 Evgeniy
    Просто задолбал HTML. :)
    Может пригодиться если создаёшь целый сайт на флеше, на 100% площади всего окна. Мне просто приятно наблюдать …/index.swf?param=value
    Явный минус – вы не можете самостоятельно определить отсутствие установленного флеш плеера у пользователя.
    Явных плюсов пока нет.

  6. Фигасе… о_0
    Это мегакруто. Молодец!!!!

  7. Alex:

    2 Admin:

    достаточно в обертке сказать:

    body { margin: 0px; overflow:hidden }
    html, body {width:100%; height: 100% }

    и получается флеш на целую страничку

  8. Alex:

    да, и самому флешевому объекту ширину и высоту по 100% задать

  9. А ти allowScriptAcces i allowFullScreen тестив?

  10. Dan:

    В старом Firefox (версии 1.0.х) была проблема с width=»100%» height=»100%»
    Может быть сейчас уже исправили, но раньше флешка вместо того, чтобы занимать всё окно, наоборот скукоживалась до непотребных размеров (может быть даже до 100х100 пикселей).

Добавить комментарий

Вы должны авторизоваться для отправки комментария.