Хакинг BPMonline: доступ к буферу обмена ОС

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

1) Каждый браузер имеет своё контекстное меню, которое имеет доступ к странице и может быть вызвано поверх логики страницы, и при случае скопировать выделенный текст элемента страницы. В Firefox это реализовано через плагин, так что вы можете взаимодействовать с ним. В IE имеется прямой доступ через window.clipboardData , что является нарушением безопасности. В остальных браузерах конкретного решения нет.

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

2) Использовать Flash (старый отработанный метод), который позволяет работать с ClipBoard .Для этого даже создан javascript код ZeroClipBoard (один js и один swf файлы), используемый многими в качестве готового решения.

Недостатки:
- Flash постепенно становится непопулярной технологией и возможно перестанет быть стандартом из-за прихода HTML5
- по умолчанию не позволяет работать с локальным сайтом, нужно настраивать Flash Player для этого
- необходимо использовать "crossdomain.xml" для разрешения кросс-доменнного доступа, т.е. иметь прямой доступ к шаблонам страниц, чего в BPmonline для проектных решений нет.

3) Использовать Silverlight, который аналогично Flash позволяет получать доступ к ClipBoard через свой контекст.
Недостатки:
- Silverlight - это тоже не чистый HTML, поэтому нелюбители этой технологии могут отказаться от данного решения, но он набирает свои обороты и становится популярным. К тому же для BPMonline - это родная технология, и её наличие обязательно на машине клиента.

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

Создаём silverlight-проект типа application, где имеются два объекта
App.xaml - инициализация
Ваш класс ClipBoard>.xaml - класс, реализующий доступ к ClipBoard

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

    x:Name="LayoutRoot" Background="White">
        >
            />
        >
         Margin="0,0,0,0"  Click="Copy"  Content="C" Foreground="#2F5B97" Height="20" Width="20" Background="#2F5B97"/>
         x:Name="txtText"  Margin="0"  Text="{Binding Text}"  Width="0" Height="0" />
    >

Почему так? Почему кнопка, а не Ctrl+C ? Дело в том, что на нажатие клавиш Ctrl+C контейнер silverlight будет реагировать только, если это нажатие будет внутри контейнера(silverlight в фокусе), а у нас нажатие будет на странице. Во-вторых, silverlight имеет одно, но существенное ограничение при доступе к ClipBoard, это можно сделать, но только из контекста Silverlight и только с подачи пользователя, т.е. должна быть нажата кнопка или любое другое событие, недоступное из javascript (private), иначе это ничем бы не отличалось от доступа напрямую из javascript (только через вызов silverlight метода).
Кнопка будет записывать текст в ClipBoard. Но как этому событию передать заданный текст. Для этого сначала надо этот текст получить. Можно вызвать javascript метод, который вернёт в качестве результата выделенный текст. Из silverlight можно вызвать javascript-метод, и передать ему параметры-значения через HtmlPage.Window.Invoke, но нельзя напрямую получить результат, поэтому я использовал текстовое поле, в которое вызванный javascript может записать результат.
В итоге получился следующий класс
  [ScriptableType]
    public partial class ClipBoardBase : UserControl
    {
        public string JavascriptFunctionName;
        public ClipBoardBase()
        {
            InitializeComponent();
            HtmlPage.RegisterScriptableObject("silverlightClipBoard", this);
            JavascriptFunctionName = "";
        }
        [ScriptableMember]
        public void CopyToTextEdit(string text)
        {
            txtText.Text = text;
        }
        [ScriptableMember]
        private void Copy(object sender, RoutedEventArgs e)
        {
            HtmlPage.Window.Invoke(JavascriptFunctionName);
            Clipboard.SetText(txtText.Text);
        }
    }

[ScriptableType] и [ScriptableMember] - обязательно для класса и метода, если хотите иметь доступ к ним со страницы
HtmlPage.RegisterScriptableObject("silverlightClipBoard", this) - под указанным именем регистрируется объект на странице и к нему можно будет обращаться(см. ниже)
JavascriptFunctionName - имя функции, которая вызывается для записи значения в текстовое поле, для дальнейшего копирования в буфер обмена.
CopyToTextEdit - будет вызвана со страницы для записи результата в текстовое поле
Copy - метод по нажатию кнопки, в конце копирует в буфер обмена

Это всё, что нужно со стороны silverlight, в результате вы получаете xap файл - контейнер silverlight, который обычно кладут в ClientBin папку сервера.
Теперь что же нужно сделать со стороны страницы
- подготовить и создать объект silverlight на странице
- подготовить и создать функцию, которая будет вычитывать копируемое в буфер значение со страницы и записывать его в текстовое поле silverlight
Всё это делается одним скриптом на PageLoadComplete с помощью Page.AddScript(script):

для добавление используется файл Silverlight.js - поставляется Microsoft, в BPMonline уже добавлен

var silverlightparent//родительский объект для silverlight;
var silverlightdiv = document.createElement('div');
b = new Object();
b.source = document.location.href.substring(0,document.location.href.lastIndexOf('/')) + '/ClientBin/ClipBoard.xap';
b.properties = {};
b.properties.version = '4.0.50303';
b.properties.width = 20;b.properties.height = 20;
b.parentElement = silverlightparent;
b.id = 'silverlightClipBoardElement';
b.events = null;
b.initParams = 'JavascriptFunctionName=silverlightClipBoardCopyToEdit';
b.context = null;
silverlightdiv.innerHTML = Silverlight.createObjectEx(b);
silverlightClipBoardElement = document.getElementById('silverlightClipBoardElement');

Silverlight.createObjectEx - cоздаёт объект на странице
b.source = относительный путь к xap файлу
b.properties = {};
b.properties.version - минимальная требуемая silverlight версия
b.properties.width- размеры
b.parentElement - родительский DOM-элемент
b.id - по нему можно найти объект, в котором находится контейнер
b.initParams - начальные параметры, будут переданы в конструктор класса, задаются в виде 'key1=value1;key2=value2', их можно вычитать, я таким образом передаю название функции, которая будет работать на стороне javascript
silverlightClipBoardElement - глобальная переменная для доступа к объекту

а теперь сама функция

silverlightClipBoardCopyToEdit = function() {
// получить значение текста для буфера обмена     
silverlightClipBoardElement.content.silverlightClipBoard.CopyToTextEdit(CopyValue);
                }

посмотрите, как можно обращаться к методу , для этого у DOM-объекта silverlightClipBoardElement обращаемся к объекту, зарегистрированному под именем silverlightClipBoard (см. конструктор класса silverlight), и далее к методу, передавая заветное CopyValue.

В общем порядок работы такой
Нажатие кнопки -> Вызов javascript-метода из метода кнопки-> Javascript вычитывает копируемое значение -> Вызова silverlight-метода и запись в текстовое поле значения -> Возврат в Javascript-метод-> Возврат в метод кнопки silverlight -> Чтение значения из текстового поля и запись его в буфер обмена.

Нравится

Поделиться

1 комментарий

Появилась штатная поддержка в браузерах.

Показать все комментарии