Commit 081b5202 authored by binji@chromium.org's avatar binji@chromium.org

[NaCl SDK] Screen capture extension for use with SDK visual testing.

BUG=none
R=sbc@chromium.org

Review URL: https://codereview.chromium.org/23458018

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@221777 0039d316-1c4b-4281-b951-d872f2087c98
parent 5b854b8f
Screenshot Extension
--------------------
This extension is to visually test the SDK examples. To use, add this to the
html:
TODO(binji): normal users shouldn't have to do this -- it should be
automatically loaded by the testing framework.
<head>
...
<script
src="chrome-extension://kbbhhngcmjcmlgcnbihfgliliemmijmj/screenshot.js">
</script>
<head>
Then in JavaScript:
var myPluginEl = document.embeds[0];
function onSuccess(dataURL) {
// dataURL is a data URL encoded PNG. You can add this to a image like this:
var image = new Image();
image.onload = function() { ... }
image.src = dataURL;
}
function onError(errorMessage) {
...
}
screenshot.captureElement(myPluginEl, onSuccess, onError);
See screenshot.js for more info on the API.
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function takeScreenshot(onScreenshot) {
console.log('Taking screenshot.');
chrome.tabs.captureVisibleTab(null, {format: 'png'}, function(img) {
console.log('Got screenshot, returning...');
onScreenshot(img);
});
}
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo.status != 'complete')
return;
chrome.tabs.executeScript(tabId,
{file: 'injected.js', runAt: 'document_start'});
});
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
takeScreenshot(sendResponse);
// Keep the sendResponse channel open, so a response can be sent
// asynchronously.
return true;
});
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Only inject once.
if (!document.body.getAttribute('screenshot_extension_injected')) {
document.body.setAttribute('screenshot_extension_injected', true);
(function() {
// Bounce message from webpage to background page.
//
// Expecting a message called with:
// window.postMessage({
// id: <a value that is passed back unchanged to the response for
// identification>,
// target: 'background'
// }, '*');
//
// When the screenshot is captured, a message will be posted to the window.
// Listen for it like this:
//
// window.addEventListener('message', function(event) {
// if (event.source !== window)
// return;
//
// if (event.data.target !== 'page')
// return;
//
// // event.data is an object:
// // {
// // id: <the id passed to the request>,
// // target: 'page',
// // data: <a data URI of MIMEtype image/png with the tab screenshot>
// // }
// //
// // or if there is an error:
// //
// // {
// // id: <the id passed to the request>,
// // target: 'page',
// // error: <an error string>
// // }
// }, false);
//
window.addEventListener('message', function(event) {
if (event.source !== window)
return;
// Ignore messages not destined for the background page.
if (event.data.target !== 'background')
return;
var id = event.data.id;
console.log('sending message: id=' + id);
chrome.runtime.sendMessage(null, {},
function(responseData) {
// Bounce response from background page back to webpage.
var lastError = chrome.runtime.lastError;
if (lastError) {
console.log('lastError: ' + lastError);
window.postMessage({id: id, target: 'page', error: lastError},
'*');
return;
}
console.log('received response: id=' + id);
window.postMessage({id: id, target: 'page', data: responseData},
'*');
});
}, false);
})();
}
{
"name": "Capture element",
"version": "1.0",
"description": "A simple extension to allow capturing a tab (or element).",
"background": {
"persistent": true,
"scripts": ["background.js"]
},
"permissions": [
"tabs", "http://*/*"
],
"web_accessible_resources": [
"screenshot.js"
],
"manifest_version": 2,
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaabMsj6diZUZr1SCQTnB37+IWGtI+BH0ZiktWMqCWVSH+HfOklfYGdH5d2i0Dd/q1K2X+ckD9vNy4p+6ewzahN6K70G4Bl5LhXuIc9B0CLMQZbWbDU9SMX05+D+99Ad7yfr5eUWgyWbQItntDRaTnuYVhkKRN2Kd6KWqzvEiUoQIDAQAB"
}
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var screenshot = (function() {
/** A map of id to pending callback. */
var callbackMap = {};
/** An array of queued requests. They will all be executed when the
* background page injects screen code into this page
*/
var queuedRequests = [];
/** The next id to assign. Used for mapping id to callback. */
var nextId = 0;
/** This is set to true when the background page injects screenshot code into
* this page
*/
var extensionInjected = false;
/** Generate a new id, which maps to the given callbacks.
*
* @param {function(string)} onSuccess
* @param {function(string)} onError
* @return {number} The id.
*/
function addCallback(onSuccess, onError) {
var id = nextId++;
callbackMap[id] = [onSuccess, onError];
return id;
}
/** Call the callback mapped to |id|.
*
* @param {number} id
* @param {boolean} success true to call the success callback, false for the
* error callback.
* @param {...} A variable number of arguments to pass to the callback.
*/
function callCallback(id, success) {
var callbacks = callbackMap[id];
if (!callbacks) {
console.log('Unknown id: ' + id);
return;
}
delete callbackMap[id];
var callback = success ? callbacks[0] : callbacks[1];
if (callback)
callback(Array.prototype.slice.call(arguments, 2));
}
/** Post a message to take a screenshot.
*
* This message will be enqueued if the extension has not yet injected the
* screenshot code.
*
* @param {number} id An id to associate with this request. When the
* screenshot is complete, the background page will return
* a result with this id.
*/
function postScreenshotMessage(id) {
if (!extensionInjected) {
queuedRequests.push(id);
return;
}
window.postMessage({id: id, target: 'background'}, '*');
}
/** Post all queued screenshot requests.
*
* Should only be called after the screenshot code has been injected by the
* background page.
*/
function postQueuedRequests() {
for (var i = 0; i < queuedRequests.length; ++i) {
var id = queuedRequests[i];
postScreenshotMessage(id);
}
queuedRequests = [];
}
/** Predicate whether the extension has injected code yet.
*
* @return {boolean}
*/
function isInjected() {
// NOTE: This attribute name must match the one in injected.js.
return document.body &&
document.body.getAttribute('screenshot_extension_injected');
}
/** Start an interval that polls for when the extension has injected code
* into this page.
*
* The extension first adds a postMessage handler to listen for requests,
* then adds an attribute to the body element. If we see this attribute, we
* know the listener is ready.
*/
function pollForInjection() {
var intervalId = window.setInterval(function() {
if (!isInjected())
return;
// Finally injected!
window.clearInterval(intervalId);
extensionInjected = true;
postQueuedRequests();
}, 100); // Every 100ms.
}
// Add a postMessage listener for when the injected code returns a result
// from the background page.
window.addEventListener('message', function(event) {
// If the message comes from another window, or is outbound (i.e.
// event.data.target === 'background'), ignore it.
if (event.source !== window || event.data.target !== 'page')
return;
var success = event.data.error === undefined;
callCallback(event.data.id, success, event.data.data);
}, false);
if (isInjected())
extensionInjected = true;
else
pollForInjection();
// Public functions.
/** Capture the current visible area of the tab as a PNG.
*
* If the request succeeds, |onSuccess| will be called with one parameter:
* the image encoded as a data URL.
*
* If the request fails, |onError| will be called with one parameter: an
* informational error message.
*
* @param {function(string)} onSuccess The success callback.
* @param {function(string)} onError The error callback.
*/
function captureTab(onSuccess, onError) {
var id = addCallback(onSuccess, onError);
postScreenshotMessage(id);
}
/** Capture the current visible area of a given element as a PNG.
*
* If the request succeeds, |onSuccess| will be called with one parameter:
* the image encoded as a data URL.
*
* If the request fails, |onError| will be called with one parameter: an
* informational error message.
*
* @param {Element} element The element to capture.
* @param {function(string)} onSuccess The success callback.
* @param {function(string)} onError The error callback.
*/
function captureElement(element, onSuccess, onError) {
var elementRect = element.getBoundingClientRect();
var elX = elementRect.left;
var elY = elementRect.top;
var elW = elementRect.width;
var elH = elementRect.height;
function onScreenCaptured(dataURL) {
// Create a canvas of the correct size.
var canvasEl = document.createElement('canvas');
canvasEl.setAttribute('width', elW);
canvasEl.setAttribute('height', elH);
var ctx = canvasEl.getContext('2d');
var imgEl = new Image();
imgEl.onload = function() {
// Draw only the element region of the image.
ctx.drawImage(imgEl, elX, elY, elW, elH, 0, 0, elW, elH);
// Extract the canvas to a new data URL, and return it via the callback.
onSuccess(canvasEl.toDataURL());
};
// Load the full screenshot into imgEl.
imgEl.src = dataURL;
}
var id = addCallback(onScreenCaptured, onError);
postScreenshotMessage(id);
}
return {
captureTab: captureTab,
captureElement: captureElement
};
})();
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment