OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 var screenshot = (function() { |
| 6 /** A map of id to pending callback. */ |
| 7 var callbackMap = {}; |
| 8 |
| 9 /** An array of queued requests. They will all be executed when the |
| 10 * background page injects screen code into this page |
| 11 */ |
| 12 var queuedRequests = []; |
| 13 |
| 14 /** The next id to assign. Used for mapping id to callback. */ |
| 15 var nextId = 0; |
| 16 |
| 17 /** This is set to true when the background page injects screenshot code into |
| 18 * this page |
| 19 */ |
| 20 var extensionInjected = false; |
| 21 |
| 22 /** Generate a new id, which maps to the given callbacks. |
| 23 * |
| 24 * @param {function(string)} onSuccess |
| 25 * @param {function(string)} onError |
| 26 * @return {number} The id. |
| 27 */ |
| 28 function addCallback(onSuccess, onError) { |
| 29 var id = nextId++; |
| 30 callbackMap[id] = [onSuccess, onError]; |
| 31 return id; |
| 32 } |
| 33 |
| 34 /** Call the callback mapped to |id|. |
| 35 * |
| 36 * @param {number} id |
| 37 * @param {boolean} success true to call the success callback, false for the |
| 38 * error callback. |
| 39 * @param {...} A variable number of arguments to pass to the callback. |
| 40 */ |
| 41 function callCallback(id, success) { |
| 42 var callbacks = callbackMap[id]; |
| 43 if (!callbacks) { |
| 44 console.log('Unknown id: ' + id); |
| 45 return; |
| 46 } |
| 47 |
| 48 delete callbackMap[id]; |
| 49 var callback = success ? callbacks[0] : callbacks[1]; |
| 50 if (callback) |
| 51 callback(Array.prototype.slice.call(arguments, 2)); |
| 52 } |
| 53 |
| 54 /** Post a message to take a screenshot. |
| 55 * |
| 56 * This message will be enqueued if the extension has not yet injected the |
| 57 * screenshot code. |
| 58 * |
| 59 * @param {number} id An id to associate with this request. When the |
| 60 * screenshot is complete, the background page will return |
| 61 * a result with this id. |
| 62 */ |
| 63 function postScreenshotMessage(id) { |
| 64 if (!extensionInjected) { |
| 65 queuedRequests.push(id); |
| 66 return; |
| 67 } |
| 68 |
| 69 window.postMessage({id: id, target: 'background'}, '*'); |
| 70 } |
| 71 |
| 72 /** Post all queued screenshot requests. |
| 73 * |
| 74 * Should only be called after the screenshot code has been injected by the |
| 75 * background page. |
| 76 */ |
| 77 function postQueuedRequests() { |
| 78 for (var i = 0; i < queuedRequests.length; ++i) { |
| 79 var id = queuedRequests[i]; |
| 80 postScreenshotMessage(id); |
| 81 } |
| 82 queuedRequests = []; |
| 83 } |
| 84 |
| 85 /** Predicate whether the extension has injected code yet. |
| 86 * |
| 87 * @return {boolean} |
| 88 */ |
| 89 function isInjected() { |
| 90 // NOTE: This attribute name must match the one in injected.js. |
| 91 return document.body && |
| 92 document.body.getAttribute('screenshot_extension_injected'); |
| 93 } |
| 94 |
| 95 /** Start an interval that polls for when the extension has injected code |
| 96 * into this page. |
| 97 * |
| 98 * The extension first adds a postMessage handler to listen for requests, |
| 99 * then adds an attribute to the body element. If we see this attribute, we |
| 100 * know the listener is ready. |
| 101 */ |
| 102 function pollForInjection() { |
| 103 var intervalId = window.setInterval(function() { |
| 104 if (!isInjected()) |
| 105 return; |
| 106 |
| 107 // Finally injected! |
| 108 window.clearInterval(intervalId); |
| 109 extensionInjected = true; |
| 110 postQueuedRequests(); |
| 111 }, 100); // Every 100ms. |
| 112 } |
| 113 |
| 114 // Add a postMessage listener for when the injected code returns a result |
| 115 // from the background page. |
| 116 window.addEventListener('message', function(event) { |
| 117 // If the message comes from another window, or is outbound (i.e. |
| 118 // event.data.target === 'background'), ignore it. |
| 119 if (event.source !== window || event.data.target !== 'page') |
| 120 return; |
| 121 |
| 122 var success = event.data.error === undefined; |
| 123 callCallback(event.data.id, success, event.data.data); |
| 124 }, false); |
| 125 |
| 126 if (isInjected()) |
| 127 extensionInjected = true; |
| 128 else |
| 129 pollForInjection(); |
| 130 |
| 131 // Public functions. |
| 132 |
| 133 /** Capture the current visible area of the tab as a PNG. |
| 134 * |
| 135 * If the request succeeds, |onSuccess| will be called with one parameter: |
| 136 * the image encoded as a data URL. |
| 137 * |
| 138 * If the request fails, |onError| will be called with one parameter: an |
| 139 * informational error message. |
| 140 * |
| 141 * @param {function(string)} onSuccess The success callback. |
| 142 * @param {function(string)} onError The error callback. |
| 143 */ |
| 144 function captureTab(onSuccess, onError) { |
| 145 var id = addCallback(onSuccess, onError); |
| 146 postScreenshotMessage(id); |
| 147 } |
| 148 |
| 149 /** Capture the current visible area of a given element as a PNG. |
| 150 * |
| 151 * If the request succeeds, |onSuccess| will be called with one parameter: |
| 152 * the image encoded as a data URL. |
| 153 * |
| 154 * If the request fails, |onError| will be called with one parameter: an |
| 155 * informational error message. |
| 156 * |
| 157 * @param {Element} element The element to capture. |
| 158 * @param {function(string)} onSuccess The success callback. |
| 159 * @param {function(string)} onError The error callback. |
| 160 */ |
| 161 function captureElement(element, onSuccess, onError) { |
| 162 var elementRect = element.getBoundingClientRect(); |
| 163 var elX = elementRect.left; |
| 164 var elY = elementRect.top; |
| 165 var elW = elementRect.width; |
| 166 var elH = elementRect.height; |
| 167 |
| 168 function onScreenCaptured(dataURL) { |
| 169 // Create a canvas of the correct size. |
| 170 var canvasEl = document.createElement('canvas'); |
| 171 canvasEl.setAttribute('width', elW); |
| 172 canvasEl.setAttribute('height', elH); |
| 173 var ctx = canvasEl.getContext('2d'); |
| 174 |
| 175 var imgEl = new Image(); |
| 176 imgEl.onload = function() { |
| 177 // Draw only the element region of the image. |
| 178 ctx.drawImage(imgEl, elX, elY, elW, elH, 0, 0, elW, elH); |
| 179 |
| 180 // Extract the canvas to a new data URL, and return it via the callback. |
| 181 onSuccess(canvasEl.toDataURL()); |
| 182 }; |
| 183 |
| 184 // Load the full screenshot into imgEl. |
| 185 imgEl.src = dataURL; |
| 186 } |
| 187 |
| 188 var id = addCallback(onScreenCaptured, onError); |
| 189 postScreenshotMessage(id); |
| 190 } |
| 191 |
| 192 return { |
| 193 captureTab: captureTab, |
| 194 captureElement: captureElement |
| 195 }; |
| 196 })(); |
OLD | NEW |