در چند سال گذشته، به چند شرکت مختلف کمک کردهام تا بااستفاده از فناوریهای مرورگر به عملکردی مشابه همرسانی صفحهنمایش دست یابند. از تجربه من، پیادهسازی VNC صرفاً در فناوریهای پلاتفرم وب (یعنی بدون افزونه) مشکل سختی است. چیزهای زیادی برای درنظر گرفتن و چالشهای زیادی برای غلبه کردن وجود دارد. رله کردن موقعیت اشارهگر موشواره، بازارسال کردن کلیدهای فشردهشده، و دستیابی به بازرسم رنگ ۲۴ بیتی کامل با سرعت ۶۰ فریم در ثانیه فقط چند مورد از این مشکلات است.
درحال ضبط محتوای برگه
اگر پیچیدگیهای همرسانی صفحهنمایش سنتی را حذف کنیم و روی همرسانی محتوای برگه مرورگر تمرکز کنیم، مشکل تا حد زیادی به الف) ضبط برگه قابلمشاهده در وضعیت فعلی آن، و ب) ارسال آن «قاب» ازطریق سیم ساده میشود. اساساً، ما به راهی برای عکسبرداری از DOM و همرسانی آن نیاز داریم.
بخش همرسانی آسان است. وبسوکتها توانایی بالایی در ارسال دادهها در قالبهای مختلف (رشته، JSON، دودویی) دارند. بخش عکس آنی مشکل بسیار سختتری است. پروژههایی مانند html2canvas با پیادهسازی مجدد موتور پردازش مرورگر… در جاوا اسکریپت، به ضبط صفحه HTML پرداختهاند! نمونه دیگر بازخورد Google است، هرچند متنباز نیست. این نوع پروژهها بسیار جالب هستند، اما بهطور وحشتناکی کند هستند. اگر بتوانید به نرخ ۱ فریم در ثانیه برسید خیلی خوششانس هستید، چه برسد به نرخ ۶۰ فریم در ثانیه که همه بهدنبال آن هستند.
این مقاله چند مورد از راهحلهای اثبات مفهوم موردعلاقهام را برای «همرسانی صفحهنمایش» برگه مورد بحث قرار میدهد.
روش ۱: Mutation Observers + WebSocket
یک رویکرد برای آینهسازی برگه توسط +Rafael Weinstein در اوایل امسال نشان داده شد. تکنیک او از Mutation Observers و WebSocket استفاده میکند.
اساساً، برگهای که ارائهدهنده همرسانی میکند تغییرات صفحه را رصد میکند و بااستفاده از وبسوکت، تفاوتها را به بیننده ارسال میکند. وقتی کاربر صفحه را پیمایش میکند یا با آن تعامل برقرار میکند، ناظران این تغییرات را دریافت میکنند و بااستفاده از کتابخانه خلاصه جهش رافائل آنها را به بیننده گزارش میدهند. این کار باعث میشود عملکرد دستگاه خوب بماند. کل صفحه برای هر قاب ارسال نمیشود.
همانطور که رافائل در ویدیو اشاره میکند، این فقط یک اثبات مفهوم است. Still, I think it's a neat way to combine a newer platform feature like Mutation Observers with an older one like Websockets.
روش ۲: Blob از HTMLDocument + Binary WebSocket
روش بعدی روشی است که اخیراً به ذهنم رسیده است. این روش شبیه به رویکرد Mutation Observers است، اما بهجای ارسال تفاوتهای خلاصه، یک کلون Blob از کل HTMLDocument ایجاد میکند و آن را ازطریق یک وبسوکت دودویی ارسال میکند. در اینجا مراحل راهاندازی را میبینید:
- همه نشانیهای وب صفحه را بازنویسی کنید تا مطلق شوند. این کار مانع از وجود پیوندهای خراب در داراییهای CSS و تصویر ثابت میشود.
- همسانهسازی عنصر سند صفحه:
document.documentElement.cloneNode(true); - بااستفاده از CSS
pointer-events: 'none';user-select:'none';overflow:hidden;، کلون را فقطخواندنی و غیرقابلانتخاب کنید و از پیمایش جلوگیری کنید - موقعیت پیمایش فعلی صفحه را ضبط کنید و آنها را بهعنوان مشخصههای
data-*در نسخه تکراری اضافه کنید. new Blob()را از.outerHTMLتکراری ایجاد کنید.
کد چیزی شبیه به این است (من از منبع کامل سادهسازی کردهام):
function screenshotPage() {
// 1. Rewrite current doc's imgs, css, and script URLs to be absolute before
// we duplicate. This ensures no broken links when viewing the duplicate.
urlsToAbsolute(document.images);
urlsToAbsolute(document.querySelectorAll("link[rel='stylesheet']"));
urlsToAbsolute(document.scripts);
// 2. Duplicate entire document tree.
var screenshot = document.documentElement.cloneNode(true);
// 3. Screenshot should be readyonly, no scrolling, and no selections.
screenshot.style.pointerEvents = 'none';
screenshot.style.overflow = 'hidden';
screenshot.style.userSelect = 'none'; // Note: need vendor prefixes
// 4. … read on …
// 5. Create a new .html file from the cloned content.
var blob = new Blob([screenshot.outerHTML], {type: 'text/html'});
// Open a popup to new file by creating a blob URL.
window.open(window.URL.createObjectURL(blob));
}
urlsToAbsolute() حاوی عبارتهای باقاعده سادهای برای بازنویسی نشانیهای وب نسبی/بدون طرح به نشانیهای وب مطلق است. این کار لازم است تا تصاویر، css، قلمها، و اسکریپتها هنگام مشاهده در بافت نشانی وب blob (مثلاً از مبدأ متفاوت) خراب نشوند.
آخرین تغییری که ایجاد کردم افزودن پشتیبانی از پیمایش بود. وقتی ارائهدهنده صفحه را پیمایش میکند، بیننده باید بتواند آن را دنبال کند. برای انجام این کار، موقعیتهای فعلی scrollX و scrollY را بهعنوان مشخصههای data-* در HTMLDocument تکراری ذخیره میکنم. قبلاز ایجاد Blob نهایی، کمی JS تزریق میشود که هنگام بار شدن صفحه اجرا میشود:
// 4. Preserve current x,y scroll position of this page. See addOnPageLoad().
screenshot.dataset.scrollX = window.scrollX;
screenshot.dataset.scrollY = window.scrollY;
// 4.5. When screenshot loads (e.g. in blob URL), scroll it to the same location
// of this page. Do this by appending a window.onDOMContentLoaded listener
// which pulls out the screenshot (dupe's) saved scrollX/Y state on the DOM.
var script = document.createElement('script');
script.textContent = '(' + addOnPageLoad_.toString() + ')();'; // self calling.
screenshot.querySelector('body').appendChild(script);
// NOTE: Not to be invoked directly. When the screenshot loads, scroll it
// to the same x,y location of original page.
function addOnPageLoad() {
window.addEventListener('DOMContentLoaded', function(e) {
var scrollX = document.documentElement.dataset.scrollX || 0;
var scrollY = document.documentElement.dataset.scrollY || 0;
window.scrollTo(scrollX, scrollY);
});
جعل پیمایش این تصور را ایجاد میکند که ما از بخشی از صفحه اصلی نماگرفت گرفتهایم، درحالیکه درواقع کل صفحه را کپی کردهایم و فقط موقعیت آن را تغییر دادهایم. #clever
پخش نمایشی
اما برای همرسانی برگه، باید بهطور مداوم برگه را ضبط کنیم و آن را برای بینندگان ارسال کنیم. برای این کار، یک سرور وبسوکت Node کوچک، برنامه، و بوکمارکلت نوشتهام که جریان را نشان میدهد. اگر به کد علاقهای ندارید، در اینجا ویدیو کوتاهی از نحوه عملکرد آن آورده شده است:
بهبودهای آینده
یکی از بهینهسازیها این است که کل سند در هر قاب تکرار نشود. این کار باعث اتلاف میشود و نمونه Mutation Observer بهخوبی از آن جلوگیری میکند. بهبود دیگر
مدیریت تصاویر پسزمینه CSS نسبی در urlsToAbsolute() است. این چیزی است که
دستورگان فعلی درنظر نمیگیرد.
روش ۳: Chrome Extension API + Binary WebSocket
در Google I/O 2012، رویکرد دیگری را برای همرسانی صفحهنمایش محتوای برگه مرورگر نشان دادم. اما این یکی تقلب است. این برنامه به Chrome Extension API نیاز دارد: نه جادوی HTML5 خالص.
منبع این یکی هم در Github است، اما خلاصه آن این است:
- برگه فعلی را بهعنوان dataURL با قالب png .ضبط میکند. «افزونههای Chrome» برای این کار میانای برنامهسازی کاربردی دارند
chrome.tabs.captureVisibleTab(). - dataURL را به
Blobتبدیل کنید. کمککنندهconvertDataURIToBlob()را ببینید. - با تنظیم
socket.responseType='blob'، هر Blob (قاب) را بااستفاده از وبسوکت دودویی به بیننده ارسال کنید.
مثال
این کد برای گرفتن نماگرفت از برگه فعلی بهعنوان png و ارسال قاب ازطریق وبسوکت است:
var IMG_MIMETYPE = 'images/jpeg'; // Update to image/webp when crbug.com/112957 is fixed.
var IMG_QUALITY = 80; // [0-100]
var SEND_INTERVAL = 250; // ms
var ws = new WebSocket('ws://…', 'dumby-protocol');
ws.binaryType = 'blob';
function captureAndSendTab() {
var opts = {format: IMG_MIMETYPE, quality: IMG_QUALITY};
chrome.tabs.captureVisibleTab(null, opts, function(dataUrl) {
// captureVisibleTab returns a dataURL. Decode it -> convert to blob -> send.
ws.send(convertDataURIToBlob(dataUrl, IMG_MIMETYPE));
});
}
var intervalId = setInterval(function() {
if (ws.bufferedAmount == 0) {
captureAndSendTab();
}
}, SEND_INTERVAL);
بهبودهای آینده
نرخ فریم برای این یکی بهطور شگفتانگیزی خوب است، اما میتواند حتی بهتر هم باشد. یکی از بهبودها میتواند برداشتن سربار تبدیل dataURL به Blob باشد. متأسفانه chrome.tabs.captureVisibleTab() فقط dataURL به ما میدهد. اگر Blob یا Typed Array را برمیگرداند، میتوانستیم آن را مستقیماً ازطریق websocket ارسال کنیم، بهجای اینکه خودمان تبدیل به Blob را انجام دهیم. لطفاً crbug.com/32498 را ستارهدار کنید تا این اتفاق بیفتد!
روش ۴: WebRTC - آینده واقعی
آخرین مورد اما نه کماهمیتترین!
آینده همرسانی صفحهنمایش در مرورگر با WebRTC محقق خواهد شد. در ۱۴ اوت ۲۰۱۲، تیم یک API WebRTC Tab Content Capture را برای همرسانی محتوای برگه پیشنهاد داد:
تا زمانی که این روش آماده شود، روشهای ۱ تا ۳ را داریم.
نتیجهگیری
بنابراین همرسانی زبانه مرورگر با فناوری وب امروزی امکانپذیر است!
اما… این گفته را باید با کمی تردید پذیرفت. اگرچه تکنیکهای این مقاله جالب هستند، اما از یک جهت یا جهت دیگر برای ارائه یک تجربه کاربری عالی در همرسانی کافی نیستند. همه اینها با تلاش WebRTC Tab Content Capture تغییر خواهد کرد، اما تا زمانی که این امر محقق شود، ما با افزونههای مرورگر یا راهحلهای محدودی مانند آنچه در اینجا پوشش داده شده است، باقی میمانیم.
تکنیکهای بیشتری دارید؟ نظری پست کنید!