Release 5.2
diff --git a/src/js/account.js b/src/js/account.js
new file mode 100644
index 0000000..d8ddd0d
--- /dev/null
+++ b/src/js/account.js
@@ -0,0 +1,75 @@
+/**
+ * Create a user.
+ * @param {Object} user
+ * properties: id, name, accessToken, expires, accessTokenSecret, albumId
+ */
+var User = function(user) {
+ for (var prop in user) {
+ this[prop] = user[prop];
+ }
+};
+
+var Account = {
+
+ getUsers: function(siteId) {
+ var users = localStorage.getItem(siteId + '_userInfo');
+ if (users) {
+ users = JSON.parse(users);
+ for (var id in users) {
+ // Remove expired user.
+ if (Account.isExpires(users[id])) {
+ delete users[id];
+ }
+ }
+ localStorage.setItem(siteId + '_userInfo', JSON.stringify(users));
+ return users;
+ }
+ return {};
+ },
+
+ getUser: function(siteId, userId) {
+ var users = Account.getUsers(siteId);
+ var user = users[userId];
+ if (user && Account.isExpires(user)) {
+ Account.removeUser(siteId, userId);
+ return null;
+ } else {
+ return user;
+ }
+ },
+
+ addUser: function(siteId, user) {
+ var users = Account.getUsers(siteId);
+ var userId = user.id;
+ if (!users[userId]) {
+ users[userId] = user;
+ users = JSON.stringify(users);
+ localStorage.setItem(siteId + '_userInfo', users);
+ }
+ },
+
+ updateUser: function(siteId, user) {
+ var users = Account.getUsers(siteId);
+ var userId = user.id;
+ if (users && users[userId]) {
+ users[userId] = user;
+ users = JSON.stringify(users);
+ localStorage.setItem(siteId + '_userInfo', users);
+ }
+ },
+
+ removeUser: function(siteId, userId) {
+ var users = Account.getUsers(siteId);
+ delete users[userId];
+ users = JSON.stringify(users);
+ localStorage.setItem(siteId + '_userInfo', users);
+ },
+
+ isExpires: function(user) {
+ var expires = user.expires;
+ if (expires) {
+ return new Date().getTime() >= expires;
+ }
+ return false;
+ }
+};
\ No newline at end of file
diff --git a/src/js/ajax.js b/src/js/ajax.js
new file mode 100644
index 0000000..2f3d4a5
--- /dev/null
+++ b/src/js/ajax.js
@@ -0,0 +1,185 @@
+(function(){
+ /**
+ * ajax is a encapsulated function that used to send data to server
+ * asynchronously. It uses XMLHttpRequest object to send textual or binary
+ * data through HTTP method GET, POST etc. It can custom request method,
+ * request header. Response can be parsed automatically by MIME type of
+ * response's Content-type, and it can handle success, error or progress event
+ * in course of sending request and retrieving response.
+ * @param {Object} option
+ */
+ function ajax(option) {
+ if (arguments.length < 1 || option.constructor != Object)
+ throw new Error('Bad parameter.');
+ var url = option.url;
+ var success = option.success;
+ var complete = option.complete;
+ if (!url || !(success || complete))
+ throw new Error('Parameter url and success or complete are required.');
+
+ var parameters = option.parameters || {};
+ var method = option.method || 'GET';
+ var status = option.status;
+ var headers = option.headers || {};
+ var data = option.data || null;
+ var multipartData = option.multipartData;
+ var queryString = constructQueryString(parameters);
+
+ if (multipartData) {
+ var boundary = multipartData.boundary || 'XMLHttpRequest2';
+ method = 'POST';
+ var multipartDataString;
+ var contentType = headers['Content-Type'] || 'multipart/form-data';
+ if (contentType.indexOf('multipart/form-data') == 0) {
+ headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary;
+ multipartDataString = constructMultipartFormData(multipartData, boundary,
+ parameters);
+ } else if (contentType.indexOf('multipart/related') == 0) {
+ headers['Content-Type'] = 'multipart/related; boundary=' + boundary;
+ multipartDataString = constructMultipartRelatedData(boundary,
+ multipartData.dataList);
+ }
+
+ data = constructBufferData(multipartDataString);
+ } else {
+ if (queryString)
+ url += '?' + queryString;
+ }
+
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ var statusCode = xhr.status;
+ var parsedResponse = parseResponse(xhr);
+ if (complete)
+ complete(statusCode, parsedResponse);
+ if (success && (statusCode == 200 || statusCode == 304)) {
+ success(parsedResponse);
+ } else if (status) {
+ if (status[statusCode]) {
+ // Call specified status code handler
+ status[statusCode](parsedResponse);
+ } else if (status['others']) {
+ // Call others status code handler
+ status['others'](parsedResponse, statusCode);
+ }
+ }
+ }
+ };
+
+ // Handle request progress
+ var progress = option.progress;
+ if (progress) {
+ xhr.upload.addEventListener('progress', function(e) {
+ // lengthComputable return true when the length of the progress is known
+ if (e.lengthComputable) {
+ progress(e.loaded, e.total);
+ }
+ }, false);
+ }
+ // Set request header
+ for (var headerKey in headers) {
+ xhr.setRequestHeader(headerKey, headers[headerKey]);
+ }
+
+ xhr.send(data);
+ }
+
+ function constructQueryString(parameters) {
+ var tmpParameter = [];
+ for(var name in parameters) {
+ var value = parameters[name];
+ if (value.constructor == Array) {
+ value.forEach(function(val) {
+ tmpParameter.push(name + '=' + val);
+ });
+ } else {
+ tmpParameter.push(name + '=' + value);
+ }
+ }
+ return tmpParameter.join('&');
+ }
+
+ // Parse response data according to content type of response
+ function parseResponse(xhr) {
+ var ct = xhr.getResponseHeader("content-type");
+ if (typeof ct == 'string') {
+ if (ct.indexOf('xml') >= 0)
+ return xhr.responseXML;
+ else if (ct.indexOf('json') >= 0)
+ return JSON.parse(xhr.responseText);
+ }
+ return xhr.responseText;
+ }
+
+ /**
+ * Construct multipart/form-data formatted data string.
+ * @param {Object} binaryData binary data
+ * @param {String} boundary boundary of parts
+ * @param {Object} otherParameters other text parameters
+ */
+ function constructMultipartFormData(binaryData, boundary, otherParameters) {
+ var commonHeader = 'Content-Disposition: form-data; ';
+ var data = [];
+ for (var key in otherParameters) {
+
+ // Add boundary of one header part
+ data.push('--' + boundary + '\r\n');
+
+ // Add same Content-Disposition information
+ data.push(commonHeader);
+ data.push('name="' + key + '"\r\n\r\n' + otherParameters[key] + '\r\n');
+ }
+
+ // Construct file data header
+ data.push('--' + boundary + '\r\n');
+ data.push(commonHeader);
+
+ data.push('name="' + (binaryData.name || 'binaryfilename') + '"; ');
+ data.push('filename=\"' + binaryData.value + '\"\r\n');
+ data.push('Content-type: ' + binaryData.type + '\r\n\r\n');
+ data.push(binaryData.data + '\r\n');
+
+ data.push('--' + boundary + '--\r\n');
+ return data.join('');
+ }
+
+ function constructBufferData(dataString, contentType) {
+ var len = dataString.length;
+
+ // Create a 8-bit unsigned integer ArrayBuffer view
+ var data = new Uint8Array(len);
+ for (var i = 0; i < len; i++) {
+ data[i] = dataString.charCodeAt(i);
+ }
+
+ return data.buffer
+ }
+
+ function constructMultipartRelatedData(boundary, dataList) {
+ var result = [];
+ dataList.forEach(function(data) {
+ result.push('--' + boundary + '\r\n');
+ result.push('Content-Type: ' + data.contentType + '\r\n\r\n');
+ result.push(data.data + '\r\n');
+ });
+ result.push('--' + boundary + '--\r\n');
+ return result.join('');
+ }
+
+ ajax.encodeForBinary = function(string) {
+ string = encodeURI(string).replace(/%([A-Z0-9]{2})/g, '%u00$1');
+ return unescape(string);
+ };
+
+ ajax.convertEntityString = function(string) {
+ var entitychars = ['<', '>', '&', '"', '\''];
+ var entities = ['<', '>', '&', '"', '''];
+ entitychars.forEach(function(character, index) {
+ string = string.replace(character, entities[index]);
+ });
+ return string;
+ };
+ window.ajax = ajax;
+})();
\ No newline at end of file
diff --git a/src/js/background.js b/src/js/background.js
new file mode 100644
index 0000000..e3c030e
--- /dev/null
+++ b/src/js/background.js
@@ -0,0 +1,323 @@
+// Copyright (c) 2009 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 = {
+ tab: 0,
+ canvas: document.createElement("canvas"),
+ startX: 0,
+ startY: 0,
+ scrollX: 0,
+ scrollY: 0,
+ docHeight: 0,
+ docWidth: 0,
+ visibleWidth: 0,
+ visibleHeight: 0,
+ scrollXCount: 0,
+ scrollYCount: 0,
+ scrollBarX: 17,
+ scrollBarY: 17,
+ captureStatus: true,
+ screenshotName: null,
+
+ handleHotKey: function(keyCode) {
+ if (HotKey.isEnabled()) {
+ switch (keyCode) {
+ case HotKey.getCharCode('area'):
+ screenshot.showSelectionArea();
+ break;
+ case HotKey.getCharCode('viewport'):
+ screenshot.captureWindow();
+ break;
+ case HotKey.getCharCode('fullpage'):
+ screenshot.captureWebpage();
+ break;
+ case HotKey.getCharCode('screen'):
+ screenshot.captureScreen();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Receive messages from content_script, and then decide what to do next
+ */
+ addMessageListener: function() {
+ chrome.extension.onMessage.addListener(function(request, sender, response) {
+ var obj = request;
+ var hotKeyEnabled = HotKey.isEnabled();
+ switch (obj.msg) {
+ case 'capture_hot_key':
+ screenshot.handleHotKey(obj.keyCode);
+ break;
+ case 'capture_selected':
+ screenshot.captureSelected();
+ break;
+ case 'capture_window':
+ if (hotKeyEnabled) {
+ screenshot.captureWindow();
+ }
+ break;
+ case 'capture_area':
+ if (hotKeyEnabled) {
+ screenshot.showSelectionArea();
+ }
+ break;
+ case 'capture_webpage':
+ if (hotKeyEnabled) {
+ screenshot.captureWebpage();
+ }
+ break;
+ }
+ });
+ },
+
+ /**
+ * Send the Message to content-script
+ */
+ sendMessage: function(message, callback) {
+ chrome.tabs.getSelected(null, function(tab) {
+ chrome.tabs.sendMessage(tab.id, message, callback);
+ });
+ },
+
+ showSelectionArea: function() {
+ screenshot.sendMessage({msg: 'show_selection_area'}, null);
+ },
+
+ captureWindow: function() {
+ screenshot.sendMessage({msg: 'capture_window'},
+ screenshot.onResponseVisibleSize);
+ },
+
+ captureSelected: function() {
+ screenshot.sendMessage({msg: 'capture_selected'},
+ screenshot.onResponseVisibleSize);
+ },
+
+ captureWebpage: function() {
+ screenshot.sendMessage({msg: 'scroll_init'},
+ screenshot.onResponseVisibleSize);
+ },
+
+ onResponseVisibleSize: function(response) {
+ switch (response.msg) {
+ case 'capture_window':
+ screenshot.captureVisible(response.docWidth, response.docHeight);
+ break;
+ case 'scroll_init_done':
+ screenshot.startX = response.startX,
+ screenshot.startY = response.startY,
+ screenshot.scrollX = response.scrollX,
+ screenshot.scrollY = response.scrollY,
+ screenshot.canvas.width = response.canvasWidth;
+ screenshot.canvas.height = response.canvasHeight;
+ screenshot.visibleHeight = response.visibleHeight,
+ screenshot.visibleWidth = response.visibleWidth,
+ screenshot.scrollXCount = response.scrollXCount;
+ screenshot.scrollYCount = response.scrollYCount;
+ screenshot.docWidth = response.docWidth;
+ screenshot.docHeight = response.docHeight;
+ screenshot.zoom = response.zoom;
+ setTimeout("screenshot.captureAndScroll()", 100);
+ break;
+ case 'scroll_next_done':
+ screenshot.scrollXCount = response.scrollXCount;
+ screenshot.scrollYCount = response.scrollYCount;
+ setTimeout("screenshot.captureAndScroll()", 100);
+ break;
+ case 'scroll_finished':
+ screenshot.captureAndScrollDone();
+ break;
+ }
+ },
+
+ captureSpecialPage: function() {
+ var formatParam = localStorage.screenshootQuality || 'png';
+ chrome.tabs.captureVisibleTab(
+ null, {format: formatParam, quality: 50}, function(data) {
+ var image = new Image();
+ image.onload = function() {
+ screenshot.canvas.width = image.width;
+ screenshot.canvas.height = image.height;
+ var context = screenshot.canvas.getContext("2d");
+ context.drawImage(image, 0, 0);
+ screenshot.postImage();
+ };
+ image.src = data;
+ });
+ },
+
+ captureScreenCallback: function(data) {
+ var image = new Image();
+ image.onload = function() {
+ screenshot.canvas.width = image.width;
+ screenshot.canvas.height = image.height;
+ var context = screenshot.canvas.getContext("2d");
+ context.drawImage(image, 0, 0);
+ screenshot.postImage();
+ };
+ image.src = "data:image/bmp;base64," + data;
+ },
+
+ /**
+ * Use drawImage method to slice parts of a source image and draw them to
+ * the canvas
+ */
+ capturePortion: function(x, y, width, height,
+ visibleWidth, visibleHeight, docWidth, docHeight) {
+ var formatParam = localStorage.screenshootQuality || 'png';
+ chrome.tabs.captureVisibleTab(
+ null, {format: formatParam, quality: 50}, function(data) {
+ var image = new Image();
+ image.onload = function() {
+ var curHeight = image.width < docWidth ?
+ image.height - screenshot.scrollBarY : image.height;
+ var curWidth = image.height < docHeight ?
+ image.width - screenshot.scrollBarX : image.width;
+ var zoomX = curWidth / visibleWidth;
+ var zoomY = curHeight / visibleHeight;
+ screenshot.canvas.width = width * zoomX;
+ screenshot.canvas.height = height * zoomY;
+ var context = screenshot.canvas.getContext("2d");
+ context.drawImage(image, x * zoomX, y * zoomY, width * zoomX,
+ height * zoomY, 0, 0, width * zoomX, height * zoomY);
+ screenshot.postImage();
+ };
+ image.src = data;
+ });
+ },
+
+ captureVisible: function(docWidth, docHeight) {
+ var formatParam = localStorage.screenshootQuality || 'png';
+ chrome.tabs.captureVisibleTab(
+ null, {format: formatParam, quality: 50}, function(data) {
+ var image = new Image();
+ image.onload = function() {
+ var width = image.height < docHeight ?
+ image.width - 17 : image.width;
+ var height = image.width < docWidth ?
+ image.height - 17 : image.height;
+ screenshot.canvas.width = width;
+ screenshot.canvas.height = height;
+ var context = screenshot.canvas.getContext("2d");
+ context.drawImage(image, 0, 0, width, height, 0, 0, width, height);
+ screenshot.postImage();
+ };
+ image.src = data;
+ });
+ },
+
+ /**
+ * Use the drawImage method to stitching images, and render to canvas
+ */
+ captureAndScroll: function() {
+ var formatParam = localStorage.screenshootQuality || 'png';
+ chrome.tabs.captureVisibleTab(
+ null, {format: formatParam, quality: 50}, function(data) {
+ var image = new Image();
+ image.onload = function() {
+ var context = screenshot.canvas.getContext('2d');
+ var width = 0;
+ var height = 0;
+
+ // Get scroll bar's width.
+ screenshot.scrollBarY =
+ screenshot.visibleHeight < screenshot.docHeight ? 17 : 0;
+ screenshot.scrollBarX =
+ screenshot.visibleWidth < screenshot.docWidth ? 17 : 0;
+
+ // Get visible width and height of capture result.
+ var visibleWidth =
+ (image.width - screenshot.scrollBarY < screenshot.canvas.width ?
+ image.width - screenshot.scrollBarY : screenshot.canvas.width);
+ var visibleHeight =
+ (image.height - screenshot.scrollBarX < screenshot.canvas.height ?
+ image.height - screenshot.scrollBarX : screenshot.canvas.height);
+
+ // Get region capture start x coordinate.
+ var zoom = screenshot.zoom;
+ var x1 = screenshot.startX - Math.round(screenshot.scrollX * zoom);
+ var x2 = 0;
+ var y1 = screenshot.startY - Math.round(screenshot.scrollY * zoom);
+ var y2 = 0;
+
+ if ((screenshot.scrollYCount + 1) * visibleWidth >
+ screenshot.canvas.width) {
+ width = screenshot.canvas.width % visibleWidth;
+ x1 = (screenshot.scrollYCount + 1) * visibleWidth -
+ screenshot.canvas.width + screenshot.startX - screenshot.scrollX;
+ } else {
+ width = visibleWidth;
+ }
+
+ if ((screenshot.scrollXCount + 1) * visibleHeight >
+ screenshot.canvas.height) {
+ height = screenshot.canvas.height % visibleHeight;
+ if ((screenshot.scrollXCount + 1) * visibleHeight +
+ screenshot.scrollY < screenshot.docHeight) {
+ y1 = 0;
+ } else {
+ y1 = (screenshot.scrollXCount + 1) * visibleHeight +
+ screenshot.scrollY - screenshot.docHeight;
+ }
+
+ } else {
+ height = visibleHeight;
+ }
+ x2 = screenshot.scrollYCount * visibleWidth;
+ y2 = screenshot.scrollXCount * visibleHeight;
+ context.drawImage(image, x1, y1, width, height, x2, y2, width, height);
+ screenshot.sendMessage({msg: 'scroll_next', visibleWidth: visibleWidth,
+ visibleHeight: visibleHeight}, screenshot.onResponseVisibleSize);
+ };
+ image.src = data;
+ });
+ },
+
+ captureAndScrollDone: function() {
+ screenshot.postImage();
+ },
+
+ /**
+ * Post the image to 'showimage.html'
+ */
+ postImage: function() {
+ chrome.tabs.getSelected(null, function(tab) {
+ screenshot.tab = tab;
+ });
+ var date = new Date();
+ screenshot.screenshotName = "Screenshot "+dateFormat(date, 'Y-m-d H.i.s');
+ chrome.tabs.create({'url': 'showimage.html'});
+ var popup = chrome.extension.getViews({type: 'popup'})[0];
+ if (popup)
+ popup.close();
+ },
+
+ isThisPlatform: function(operationSystem) {
+ return navigator.userAgent.toLowerCase().indexOf(operationSystem) > -1;
+ },
+
+ executeScriptsInExistingTabs: function() {
+ chrome.windows.getAll(null, function(wins) {
+ for (var j = 0; j < wins.length; ++j) {
+ chrome.tabs.getAllInWindow(wins[j].id, function(tabs) {
+ for (var i = 0; i < tabs.length; ++i) {
+ if (tabs[i].url.indexOf("chrome://") != 0) {
+ chrome.tabs.executeScript(tabs[i].id, { file: 'js/page.js' });
+ chrome.tabs.executeScript(tabs[i].id, { file: 'js/shortcut.js' });
+ }
+ }
+ });
+ }
+ });
+ },
+
+ init: function() {
+ localStorage.screenshootQuality = localStorage.screenshootQuality || 'png';
+ screenshot.executeScriptsInExistingTabs();
+ screenshot.addMessageListener();
+ }
+};
+
+screenshot.init();
diff --git a/src/js/editor.js b/src/js/editor.js
new file mode 100644
index 0000000..0d284ff
--- /dev/null
+++ b/src/js/editor.js
@@ -0,0 +1,240 @@
+function Canvas() {}
+
+Canvas.prototype.drawStrokeRect = function(
+ ctx, color, x, y, width, height, lineWidth) {
+ ctx.strokeStyle = color;
+ ctx.lineWidth = lineWidth;
+ ctx.strokeRect(x, y, width, height);
+}
+
+Canvas.prototype.drawFillRect = function(ctx, color, x, y, width, height) {
+ ctx.fillStyle = color;
+ ctx.fillRect(x, y, width, height);
+}
+
+Canvas.prototype.drawEllipse = function(
+ ctx, color, x, y, xAxis, yAxis, lineWidth, type) {
+ var startX = x + xAxis;
+ var startY = y;
+ ctx.beginPath();
+ ctx.lineWidth = lineWidth;
+ ctx.moveTo(startX, startY);
+ for (var i = 0; i <= 360; i++) {
+ var degree = i * Math.PI / 180;
+ startX = x + (xAxis - 2) * Math.cos(degree);
+ startY = y - (yAxis - 2) * Math.sin(degree);
+ ctx.lineTo(startX, startY);
+ }
+ if (type == 'rect') {
+ ctx.fillStyle = changeColorToRgba(color, 0.5);
+ ctx.fill();
+ } else if (type == 'border') {
+ ctx.strokeStyle = color;
+ ctx.stroke();
+ }
+ ctx.closePath();
+}
+
+// Divide an entire phrase in an array of phrases, all with the max pixel
+// length given.
+Canvas.prototype.getLines = function(ctx, text, width, font) {
+ var words = text.split(" ");
+ var lines = [];
+ var lastLine = "";
+ var measure = 0;
+ ctx.font = font;
+ for (var i = 0; i < words.length; i++) {
+ var word = words[i];
+ measure = ctx.measureText(lastLine + word).width;
+ if (measure <= width || word == "") {
+ lastLine += word + " ";
+ } else {
+ if (lastLine != "")
+ lines.push(lastLine);
+
+ // break the word if necessary
+ measure = ctx.measureText(word).width;
+ if (measure <= width) {
+ lastLine = word + " ";
+ } else {
+ lastLine = word[0];
+ for (var j = 1; j < word.length; j++) {
+ measure = ctx.measureText(lastLine + word[j]).width;
+ if (measure <= width) {
+ lastLine += word[j];
+ } else {
+ lines.push(lastLine);
+ lastLine = word[j];
+ }
+ }
+ lastLine += " ";
+ }
+ }
+ }
+ if (lastLine != "")
+ lines.push(lastLine);
+ return lines;
+}
+
+Canvas.prototype.setText = function(
+ ctx, text, color, fontSize, fontFamily, lineHeight, x, y, width) {
+ ctx.textBaseline = 'top';
+ ctx.fillStyle = color;
+ ctx.font = fontSize + ' ' + fontFamily;
+ ctx.lineHeight = lineHeight;
+ var lines = Canvas.prototype.getLines(ctx, text, width - 2, ctx.font);
+ for (var i = 0; i < lines.length; i++)
+ ctx.fillText(lines[i], x, y + lineHeight * i, width);
+}
+
+Canvas.prototype.drawLine = function(
+ ctx, color, lineCap, lineWidth, startX, startY, endX, endY) {
+ ctx.beginPath();
+ ctx.moveTo(startX, startY);
+ ctx.strokeStyle = color;
+ ctx.lineWidth = lineWidth;
+ ctx.lineCap = lineCap;
+ ctx.lineTo(endX, endY);
+ ctx.closePath();
+ ctx.stroke();
+}
+
+Canvas.prototype.drawArrow = function(
+ ctx, color, lineWidth, arrowWidth, arrowHeight, lineCap,
+ startX, startY, endX, endY) {
+ var arrowCoordinates = calculateArrowCoordinates(
+ arrowWidth, arrowHeight,startX, startY, endX, endY);
+ ctx.beginPath();
+ ctx.strokeStyle = color;
+ ctx.lineWidth = lineWidth;
+ ctx.lineCap = lineCap;
+ ctx.moveTo(startX, startY);
+ ctx.lineTo(endX, endY);
+ ctx.lineTo(arrowCoordinates.p1.x, arrowCoordinates.p1.y);
+ ctx.moveTo(endX, endY);
+ ctx.lineTo(arrowCoordinates.p2.x, arrowCoordinates.p2.y);
+ ctx.closePath();
+ ctx.stroke();
+}
+
+Canvas.prototype.drawRoundedRect = function(
+ ctx, color, x, y, width, height, radius, type) {
+ ctx.beginPath();
+ ctx.moveTo(x, y + radius);
+ ctx.lineTo(x, y + height - radius);
+ ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
+ ctx.lineTo(x + width - radius, y + height);
+ ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
+ ctx.lineTo(x + width, y + radius);
+ ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
+ ctx.lineTo(x + radius, y);
+ ctx.quadraticCurveTo(x, y, x, y + radius);
+ if (type == 'rect') {
+ ctx.fillStyle = changeColorToRgba(color, 0.5);
+ ctx.fill();
+ } else if (type == 'border') {
+ ctx.strokeStyle = color;
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ }
+ ctx.closePath();
+}
+
+Canvas.prototype.blurImage = function(
+ realCanvas, simulateCanvas, layerId, startX, startY, endX, endY) {
+ var x = startX < endX ? startX : endX;
+ var y = startY < endY ? startY : endY;
+ var width = Math.abs(endX - startX - 1);
+ var height = Math.abs(endY - startY - 1);
+ simulateCanvas.width = $(layerId).clientWidth + 10;
+ simulateCanvas.height = $(layerId).clientHeight + 10;
+ var ctx = simulateCanvas.getContext('2d');
+ try {
+ ctx.drawImage(realCanvas, x, y, width, height, 0, 0, width, height);
+ } catch (error) {
+ console.log(error + ', width : height = ' + width + ' : ' + height);
+ }
+ var imageData = ctx.getImageData(0, 0, width, height);
+ imageData = this.boxBlur(imageData, width, height, 10);
+ ctx.putImageData(imageData, 0, 0);
+}
+
+Canvas.prototype.boxBlur = function(image, width, height, count) {
+ var j;
+ var pix = image.data;
+ var inner = 0;
+ var outer = 0;
+ var step = 0;
+ var rowOrColumn;
+ var nextPosition;
+ var nowPosition;
+ for(rowOrColumn = 0; rowOrColumn < 2; rowOrColumn++) {
+ if (rowOrColumn) {
+ // column blurring
+ outer = width;
+ inner = height;
+ step = width * 4;
+ } else {
+ // Row blurring
+ outer = height;
+ inner = width;
+ step = 4;
+ }
+ for (var i = 0; i < outer; i++) {
+ // Calculate for r g b a
+ nextPosition = (rowOrColumn == 0 ? (i * width * 4) : (4 * i));
+ for (var k = 0; k < 4; k++) {
+ nowPosition = nextPosition + k;
+ var pixSum = 0;
+ for(var m = 0; m < count; m++) {
+ pixSum += pix[nowPosition + step * m];
+ }
+ pix[nowPosition] = pix[nowPosition + step] =
+ pix[nowPosition + step * 2] = Math.floor(pixSum/count);
+ for (j = 3; j < inner-2; j++) {
+ pixSum = Math.max(0, pixSum - pix[nowPosition + (j - 2) * step]
+ + pix[nowPosition + (j + 2) * step]);
+ pix[nowPosition + j * step] = Math.floor(pixSum/count);
+ }
+ pix[nowPosition + j * step] = pix[nowPosition + (j + 1) * step] =
+ Math.floor(pixSum / count);
+ }
+ }
+ }
+ return image;
+}
+
+function changeColorToRgba(color, opacity) {
+ var sColor = color.toLowerCase();
+ var sColorChange = [];
+ for (var i = 1; i < sColor.length; i += 2) {
+ sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
+ }
+ return "rgba(" + sColorChange.join(",") + "," + opacity + ")";
+}
+
+// Calculate coordinates of arrow
+function calculateArrowCoordinates(
+ arrowWidth, arrowHeight, startX, startY, endX, endY) {
+ var p1 = function() {
+ var x = startX - endX;
+ var y = startY - endY;
+ var hypotenuse = Math.sqrt(x * x + y * y);
+ hypotenuse = (hypotenuse == 0 ? arrowHeight : hypotenuse);
+ var dx = Math.round(x / hypotenuse * arrowHeight);
+ var dy = Math.round(y / hypotenuse * arrowHeight);
+ return {x: endX + dx, y: endY + dy};
+ }
+
+ var p2 = function(p1, direct) {
+ var x = p1.x - startX;
+ var y = p1.y - startY;
+ var hypotenuse = Math.sqrt(x * x + y * y);
+ hypotenuse = (hypotenuse == 0 ? arrowHeight : hypotenuse);
+ var dx = Math.round((y / hypotenuse * arrowWidth) * direct);
+ var dy = Math.round((x / hypotenuse * arrowWidth) * direct);
+ return {x: p1.x + dx, y: p1.y - dy }
+ }
+
+ return {p1:p2(p1(), 1), p2: p2(p1(), -1) } ;
+}
diff --git a/src/js/facebook.js b/src/js/facebook.js
new file mode 100644
index 0000000..68cb091
--- /dev/null
+++ b/src/js/facebook.js
@@ -0,0 +1,139 @@
+const FB_APP_ID = CURRENT_LOCALE == 'zh_CN' ? 170328509685996 : 118170701590738;
+const FB_REDIRECT_URI = 'http://www.facebook.com/connect/login_success.html';
+const FB_PERMISSION = 'offline_access,user_photos,publish_stream';
+const FB_ACCESS_TOKEN_URL = 'https://www.facebook.com/dialog/oauth';
+const FB_PHOTO_UPLOAD_URL = 'https://graph.facebook.com/me/photos';
+const FB_USER_INFO_URL = 'https://graph.facebook.com/me';
+const FB_LOGOUT_URL = 'http://m.facebook.com/logout.php?confirm=1';
+
+var Facebook = {
+ siteId: 'facebook',
+ redirectUrl: FB_REDIRECT_URI,
+ currentUserId: null,
+ accessTokenCallback: null,
+
+ isRedirectUrl: function(url) {
+ return url.indexOf(FB_REDIRECT_URI) == 0;
+ },
+
+ getAccessToken: function(callback) {
+ Facebook.accessTokenCallback = callback;
+ var url = FB_ACCESS_TOKEN_URL + '?client_id=' + FB_APP_ID +
+ '&redirect_uri=' + FB_REDIRECT_URI + '&scope=' + FB_PERMISSION +
+ '&response_type=token';
+ chrome.tabs.create({url: url});
+ },
+
+ parseAccessToken: function(url) {
+ var queryString = url.split('#')[1] || url.split('?')[1];
+ var queries = queryString.split('&');
+ var queryMap = {};
+ queries.forEach(function(pair) {
+ queryMap[pair.split('=')[0]] = pair.split('=')[1];
+ });
+ var accessToken = queryMap['access_token'];
+ if (accessToken) {
+ var user = new User({
+ accessToken: accessToken
+ });
+ Facebook.accessTokenCallback('success', user);
+ } else if (queryMap['error']) {
+ Facebook.accessTokenCallback('failure', 'user_denied');
+ }
+ Facebook.accessTokenCallback = null;
+ },
+
+ getUserInfo: function(user, callback) {
+ ajax({
+ url: FB_USER_INFO_URL,
+ parameters: {
+ 'access_token': user.accessToken
+ },
+ success: function(userInfo) {
+ userInfo = JSON.parse(userInfo);
+ if (callback) {
+ user.id = userInfo.id;
+ user.name = userInfo.name;
+ callback('success', user);
+ }
+ },
+ status: {
+ others: function() {
+ if (callback)
+ callback('failure', 'failed_to_get_user_info');
+ }
+ }
+ });
+ },
+
+ upload: function(user, caption, imageData, callback) {
+ caption = ajax.encodeForBinary(caption);
+ var params = {
+ 'access_token': user.accessToken,
+ message: caption
+ };
+
+ var binaryData = {
+ boundary: MULTIPART_FORMDATA_BOUNDARY,
+ data: imageData,
+ value: 'test.png',
+ type: 'image/png'
+ };
+
+ ajax({
+ url: FB_PHOTO_UPLOAD_URL,
+ parameters: params,
+ multipartData: binaryData,
+ success: function(data) {
+ callback('success', JSON.parse(data).id);
+ },
+ status: {
+ others: function(data) {
+ var message;
+ if (data) {
+ data = JSON.parse(data);
+ if (data.error.message.indexOf('access token') >= 0) {
+ // User removed application permission
+ // {"error":{"type":"OAuthException",
+ // "message":"Error validating access token."}}
+ message = 'bad_access_token';
+ } else {
+ // {"error":{"type":"OAuthException",
+ // "message":"(#1) An unknown error occurred"}}
+ message = 'unknown_error';
+ }
+ } else {
+ message = 'failed_to_connect_to_server';
+ }
+ callback('failure', message);
+ }
+ }
+ });
+ },
+
+ getPhotoLink: function(user, photoId, callback) {
+ ajax({
+ url: 'https://graph.facebook.com/' + photoId,
+ parameters: {
+ 'access_token': user.accessToken
+ },
+ complete: function(statusCode, data) {
+ if (statusCode == 200) {
+ callback('success', JSON.parse(data).link);
+ } else {
+ callback('failure', 'failed_to_get_photo_link');
+ }
+ }
+ });
+ },
+
+ logout: function(callback) {
+ ajax({
+ url: FB_LOGOUT_URL,
+ success: function(data) {
+ if (callback)
+ callback(data);
+ }
+ });
+ }
+};
\ No newline at end of file
diff --git a/src/js/hotkey_storage.js b/src/js/hotkey_storage.js
new file mode 100644
index 0000000..9cea0aa
--- /dev/null
+++ b/src/js/hotkey_storage.js
@@ -0,0 +1,54 @@
+var HotKey = (function() {
+ return {
+ setup: function() {
+ // Default enable hot key for capture.
+ if (!localStorage.getItem('hot_key_enabled'))
+ localStorage.setItem('hot_key_enabled', true);
+
+ // Set default hot key of capture, R V H P.
+ if (!this.get('area'))
+ this.set('area', 'R');
+ if (!this.get('viewport'))
+ this.set('viewport', 'V');
+ if (!this.get('fullpage'))
+ this.set('fullpage', 'H');
+ if (!this.get('screen'))
+ this.set('screen', 'P');
+
+ var screenCaptureHotKey = this.get('screen');
+ if (this.isEnabled()) {
+ this.set('screen', '@'); // Disable hot key for screen capture.
+ }
+ },
+
+ /**
+ * Set hot key by type.
+ * @param {String} type Hot key type, must be area/viewport/fullpage/screen.
+ * @param {String} value
+ */
+ set: function(type, value) {
+ var key = type + '_capture_hot_key';
+ localStorage.setItem(key, value);
+ },
+
+ get: function(type) {
+ return localStorage.getItem(type + '_capture_hot_key');
+ },
+
+ getCharCode: function(type) {
+ return this.get(type).charCodeAt(0);
+ },
+
+ enable: function() {
+ localStorage.setItem('hot_key_enabled', true);
+ },
+
+ disable: function(bg) {
+ localStorage.setItem('hot_key_enabled', false);
+ },
+
+ isEnabled: function() {
+ return localStorage.getItem('hot_key_enabled') == 'true';
+ }
+ }
+})();
diff --git a/src/js/imgur.js b/src/js/imgur.js
new file mode 100644
index 0000000..cfc399f
--- /dev/null
+++ b/src/js/imgur.js
@@ -0,0 +1,241 @@
+(function() {
+ const IMGUR_APP_KEY = '90922ae86b91b09541d74134a5bb0f6404e0c378f';
+ const IMGUR_APP_SECRET = 'b100656ebf595b7464e428615b9bc703';
+ const IMGUR_REQUEST_TOKEN_URL = 'https://api.imgur.com/oauth/request_token';
+ const IMGUR_USER_AUTHENTICATION_URL = 'https://api.imgur.com/oauth/authorize';
+ const IMGUR_ACCESS_TOKEN_URL = 'https://api.imgur.com/oauth/access_token';
+ const IMGUR_USER_INFO_URL = 'http://api.imgur.com/2/account.json';
+ const IMGUR_PHOTO_UPLOAD_URL = 'http://api.imgur.com/2/account/images.json';
+ const IMGUR_LOGOUT_URL = 'http://imgur.com/logout';
+ const OAUTH_SIGNATURE_METHOD = 'HMAC-SHA1';
+ const OAUTH_VERSION = '1.0';
+
+ var Imgur = window.Imgur = {
+ siteId: 'imgur',
+ currentUserId: null,
+ currentUserOauthToken: '',
+ currentUserOauthTokenSecret: '',
+ accessTokenCallback: null,
+
+ isRedirectUrl: function() {},
+
+ getAuthorizationHeader: function(message, accessor) {
+ OAuth.setTimestampAndNonce(message);
+ OAuth.SignatureMethod.sign(message, accessor);
+ return OAuth.getAuthorizationHeader("", message.parameters);
+ },
+
+ getRequestToken: function(callback) {
+ Imgur.accessTokenCallback = callback;
+ var message = {
+ action: IMGUR_REQUEST_TOKEN_URL,
+ method: 'POST',
+ parameters: {
+ 'oauth_consumer_key': IMGUR_APP_KEY,
+ 'oauth_signature_method': OAUTH_SIGNATURE_METHOD,
+ 'oauth_version': OAUTH_VERSION
+ }
+ };
+ var accessor = {
+ consumerKey: IMGUR_APP_KEY,
+ consumerSecret: IMGUR_APP_SECRET
+ };
+
+ // Get oauth signature header
+ var header = Imgur.getAuthorizationHeader(message, accessor);
+
+ ajax({
+ url: IMGUR_REQUEST_TOKEN_URL,
+ method: 'POST',
+ headers: {
+ 'Authorization': header
+ },
+ success: function(response) {
+ parameters = OAuth.getParameterMap(response);
+ var oauth_token = parameters['oauth_token'];
+ var oauth_token_secret = parameters['oauth_token_secret'];
+ Imgur.currentUserOauthToken = oauth_token;
+ Imgur.currentUserOauthTokenSecret = oauth_token_secret;
+ Imgur.getUserAuthentication(oauth_token);
+ },
+ status: {
+ others: function() {
+ callback('failure', 'imgur_failed_to_get_request_token');
+ }
+ }
+ });
+ },
+
+ getUserAuthentication: function(oauth_token) {
+ var url = IMGUR_USER_AUTHENTICATION_URL + '?oauth_token=' + oauth_token +
+ '&oauth_callback=ready';
+ chrome.tabs.create({url: url}, function(tab) {
+ chrome.tabs.onUpdated.addListener(
+ function(tabId, changeInfo, _tab) {
+ if (tabId == tab.id && changeInfo.url
+ && changeInfo.url.indexOf('oauth_verifier=') > 0) {
+ chrome.tabs.remove(tabId);
+ Imgur.parseAccessToken(changeInfo.url);
+ }
+ });
+ });
+ },
+
+ parseAccessToken: function(url) {
+ var oauth_verifier = OAuth.getParameter(url, 'oauth_verifier');
+ Imgur.getAccessToken(Imgur.accessTokenCallback, oauth_verifier);
+ Imgur.accessTokenCallback = null;
+ },
+
+ getAccessToken: function(callback, oauth_verifier) {
+ if (!oauth_verifier) {
+ Imgur.getRequestToken(callback);
+ return;
+ }
+ var message = {
+ action: IMGUR_ACCESS_TOKEN_URL,
+ method: 'POST',
+ parameters: {
+ 'oauth_consumer_key': IMGUR_APP_KEY,
+ 'oauth_token': Imgur.currentUserOauthToken,
+ 'oauth_token_secret': Imgur.currentUserOauthTokenSecret,
+ 'oauth_signature_method': OAUTH_SIGNATURE_METHOD,
+ 'oauth_verifier': oauth_verifier,
+ 'oauth_version': OAUTH_VERSION
+ }
+ };
+ var accessor = {
+ consumerKey: IMGUR_APP_KEY,
+ consumerSecret: IMGUR_APP_SECRET,
+ tokenSecret: Imgur.currentUserOauthTokenSecret
+ };
+ var header = Imgur.getAuthorizationHeader(message, accessor);
+
+ ajax({
+ url: IMGUR_ACCESS_TOKEN_URL,
+ method: 'POST',
+ headers: {
+ 'Authorization': header
+ },
+ success: function(response) {
+ responseMap = OAuth.getParameterMap(response);
+ var accessToken = responseMap.oauth_token;
+ var accessTokenSecret = responseMap.oauth_token_secret;
+ var user = new User({
+ id: null,
+ accessToken: accessToken,
+ accessTokenSecret: accessTokenSecret
+ });
+
+ callback('success', user);
+ },
+ status: {
+ others: function(data) {
+ callback('failure', 'imgur_failed_to_get_access_token');
+ }
+ }
+ });
+ },
+
+ getUserInfo: function(user, callback) {
+ var url = IMGUR_USER_INFO_URL;
+ var message = {
+ action: url,
+ method: 'GET',
+ parameters: {
+ 'oauth_consumer_key': IMGUR_APP_KEY,
+ 'oauth_token': user.accessToken,
+ 'oauth_signature_method': OAUTH_SIGNATURE_METHOD,
+ 'oauth_version': OAUTH_VERSION
+ }
+ };
+
+ var accessor = {
+ consumerSecret: IMGUR_APP_SECRET,
+ tokenSecret: user.accessTokenSecret
+ };
+
+ var header = Imgur.getAuthorizationHeader(message, accessor);
+ ajax({
+ url: url,
+ method: 'GET',
+ headers: {
+ 'Authorization': header
+ },
+ success: function(data) {
+ if (callback) {
+ user.id = data.account.url;
+ user.name = data.account.url;
+ callback('success', user);
+ }
+ },
+ status: {
+ others: function(data) {
+ callback('failure', 'failed_to_get_user_info');
+ }
+ }
+ });
+ },
+
+ upload: function(user, caption, imageData, callback) {
+ caption = encodeURIComponent(caption);
+ var message = {
+ action: IMGUR_PHOTO_UPLOAD_URL,
+ method: 'POST',
+ parameters: {
+ 'oauth_consumer_key': IMGUR_APP_KEY,
+ 'oauth_token': user.accessToken,
+ 'oauth_signature_method': OAUTH_SIGNATURE_METHOD,
+ 'oauth_version': OAUTH_VERSION
+ }
+ };
+ var accessor = {
+ consumerSecret: IMGUR_APP_SECRET,
+ tokenSecret: user.accessTokenSecret
+ };
+ var header = Imgur.getAuthorizationHeader(message, accessor);
+
+ var binaryData = {
+ boundary: MULTIPART_FORMDATA_BOUNDARY,
+ name: 'image',
+ value: 'screencapture.png',
+ data: imageData,
+ type: 'image/png'
+ };
+
+ ajax({
+ url: IMGUR_PHOTO_UPLOAD_URL,
+ method: 'POST',
+ multipartData: binaryData,
+ headers: {
+ 'Authorization': header
+ },
+ success: function(response) {
+ callback('success', response.images.links.original);
+ },
+ status: {
+ others: function(err, statusCode) {
+ if (statusCode == 401) {
+ callback('failure', 'bad_access_token');
+ } else {
+ callback('failure', 'failed_to_upload_image');
+ };
+ }
+ }
+ });
+ },
+
+ getPhotoLink: function(user, photoLink, callback) {
+ callback('success', photoLink);
+ },
+
+ logout: function(callback) {
+ ajax({
+ url: IMGUR_LOGOUT_URL,
+ success: function() {
+ callback();
+ }
+ });
+ }
+ };
+})();
\ No newline at end of file
diff --git a/src/js/isLoad.js b/src/js/isLoad.js
new file mode 100644
index 0000000..77ad5c8
--- /dev/null
+++ b/src/js/isLoad.js
@@ -0,0 +1,17 @@
+function checkScriptLoad() {
+ chrome.extension.onMessage.addListener(function(request, sender, response) {
+ if (request.msg == 'is_page_capturable') {
+ try {
+ if (isPageCapturable()) {
+ response({msg: 'capturable'});
+ } else {
+ response({msg: 'uncapturable'});
+ }
+ } catch(e) {
+ response({msg: 'loading'});
+ }
+ }
+ });
+}
+
+checkScriptLoad();
diff --git a/src/js/oauth.js b/src/js/oauth.js
new file mode 100644
index 0000000..85b2785
--- /dev/null
+++ b/src/js/oauth.js
@@ -0,0 +1,551 @@
+/*
+ * Copyright 2008 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Here's some JavaScript software for implementing OAuth.
+
+ This isn't as useful as you might hope. OAuth is based around
+ allowing tools and websites to talk to each other. However,
+ JavaScript running in web browsers is hampered by security
+ restrictions that prevent code running on one website from
+ accessing data stored or served on another.
+
+ Before you start hacking, make sure you understand the limitations
+ posed by cross-domain XMLHttpRequest.
+
+ On the bright side, some platforms use JavaScript as their
+ language, but enable the programmer to access other web sites.
+ Examples include Google Gadgets, and Microsoft Vista Sidebar.
+ For those platforms, this library should come in handy.
+*/
+
+// The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by
+// http://pajhome.org.uk/crypt/md5/sha1.js
+
+/* An OAuth message is represented as an object like this:
+ {method: "GET", action: "http://server.com/path", parameters: ...}
+
+ The parameters may be either a map {name: value, name2: value2}
+ or an Array of name-value pairs [[name, value], [name2, value2]].
+ The latter representation is more powerful: it supports parameters
+ in a specific sequence, or several parameters with the same name;
+ for example [["a", 1], ["b", 2], ["a", 3]].
+
+ Parameter names and values are NOT percent-encoded in an object.
+ They must be encoded before transmission and decoded after reception.
+ For example, this message object:
+ {method: "GET", action: "http://server/path", parameters: {p: "x y"}}
+ ... can be transmitted as an HTTP request that begins:
+ GET /path?p=x%20y HTTP/1.0
+ (This isn't a valid OAuth request, since it lacks a signature etc.)
+ Note that the object "x y" is transmitted as x%20y. To encode
+ parameters, you can call OAuth.addToURL, OAuth.formEncode or
+ OAuth.getAuthorization.
+
+ This message object model harmonizes with the browser object model for
+ input elements of an form, whose value property isn't percent encoded.
+ The browser encodes each value before transmitting it. For example,
+ see consumer.setInputs in example/consumer.js.
+ */
+
+/* This script needs to know what time it is. By default, it uses the local
+ clock (new Date), which is apt to be inaccurate in browsers. To do
+ better, you can load this script from a URL whose query string contains
+ an oauth_timestamp parameter, whose value is a current Unix timestamp.
+ For example, when generating the enclosing document using PHP:
+
+ <script src="oauth.js?oauth_timestamp=<?=time()?>" ...
+
+ Another option is to call OAuth.correctTimestamp with a Unix timestamp.
+ */
+
+var OAuth; if (OAuth == null) OAuth = {};
+
+OAuth.setProperties = function setProperties(into, from) {
+ if (into != null && from != null) {
+ for (var key in from) {
+ into[key] = from[key];
+ }
+ }
+ return into;
+}
+
+OAuth.setProperties(OAuth, // utility functions
+{
+ percentEncode: function percentEncode(s) {
+ if (s == null) {
+ return "";
+ }
+ if (s instanceof Array) {
+ var e = "";
+ for (var i = 0; i < s.length; ++s) {
+ if (e != "") e += '&';
+ e += OAuth.percentEncode(s[i]);
+ }
+ return e;
+ }
+ s = encodeURIComponent(s);
+ // Now replace the values which encodeURIComponent doesn't do
+ // encodeURIComponent ignores: - _ . ! ~ * ' ( )
+ // OAuth dictates the only ones you can ignore are: - _ . ~
+ // Source: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Functions:encodeURIComponent
+ s = s.replace(/\!/g, "%21");
+ s = s.replace(/\*/g, "%2A");
+ s = s.replace(/\'/g, "%27");
+ s = s.replace(/\(/g, "%28");
+ s = s.replace(/\)/g, "%29");
+ return s;
+ }
+,
+ decodePercent: function decodePercent(s) {
+ if (s != null) {
+ // Handle application/x-www-form-urlencoded, which is defined by
+ // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
+ s = s.replace(/\+/g, " ");
+ }
+ return decodeURIComponent(s);
+ }
+,
+ /** Convert the given parameters to an Array of name-value pairs. */
+ getParameterList: function getParameterList(parameters) {
+ if (parameters == null) {
+ return [];
+ }
+ if (typeof parameters != "object") {
+ return OAuth.decodeForm(parameters + "");
+ }
+ if (parameters instanceof Array) {
+ return parameters;
+ }
+ var list = [];
+ for (var p in parameters) {
+ list.push([p, parameters[p]]);
+ }
+ return list;
+ }
+,
+ /** Convert the given parameters to a map from name to value. */
+ getParameterMap: function getParameterMap(parameters) {
+ if (parameters == null) {
+ return {};
+ }
+ if (typeof parameters != "object") {
+ return OAuth.getParameterMap(OAuth.decodeForm(parameters + ""));
+ }
+ if (parameters instanceof Array) {
+ var map = {};
+ for (var p = 0; p < parameters.length; ++p) {
+ var key = parameters[p][0];
+ if (map[key] === undefined) { // first value wins
+ map[key] = parameters[p][1];
+ }
+ }
+ return map;
+ }
+ return parameters;
+ }
+,
+ getParameter: function getParameter(parameters, name) {
+ if (parameters instanceof Array) {
+ for (var p = 0; p < parameters.length; ++p) {
+ if (parameters[p][0] == name) {
+ return parameters[p][1]; // first value wins
+ }
+ }
+ } else {
+ return OAuth.getParameterMap(parameters)[name];
+ }
+ return null;
+ }
+,
+ formEncode: function formEncode(parameters) {
+ var form = "";
+ var list = OAuth.getParameterList(parameters);
+ for (var p = 0; p < list.length; ++p) {
+ var value = list[p][1];
+ if (value == null) value = "";
+ if (form != "") form += '&';
+ form += OAuth.percentEncode(list[p][0])
+ +'='+ OAuth.percentEncode(value);
+ }
+ return form;
+ }
+,
+ decodeForm: function decodeForm(form) {
+ var list = [];
+ var nvps = form.split('&');
+ for (var n = 0; n < nvps.length; ++n) {
+ var nvp = nvps[n];
+ if (nvp == "") {
+ continue;
+ }
+ var equals = nvp.indexOf('=');
+ var name;
+ var value;
+ if (equals < 0) {
+ name = OAuth.decodePercent(nvp);
+ value = null;
+ } else {
+ name = OAuth.decodePercent(nvp.substring(0, equals));
+ value = OAuth.decodePercent(nvp.substring(equals + 1));
+ }
+ list.push([name, value]);
+ }
+ return list;
+ }
+,
+ setParameter: function setParameter(message, name, value) {
+ var parameters = message.parameters;
+ if (parameters instanceof Array) {
+ for (var p = 0; p < parameters.length; ++p) {
+ if (parameters[p][0] == name) {
+ if (value === undefined) {
+ parameters.splice(p, 1);
+ } else {
+ parameters[p][1] = value;
+ value = undefined;
+ }
+ }
+ }
+ if (value !== undefined) {
+ parameters.push([name, value]);
+ }
+ } else {
+ parameters = OAuth.getParameterMap(parameters);
+ parameters[name] = value;
+ message.parameters = parameters;
+ }
+ }
+,
+ setParameters: function setParameters(message, parameters) {
+ var list = OAuth.getParameterList(parameters);
+ for (var i = 0; i < list.length; ++i) {
+ OAuth.setParameter(message, list[i][0], list[i][1]);
+ }
+ }
+,
+ /** Fill in parameters to help construct a request message.
+ This function doesn't fill in every parameter.
+ The accessor object should be like:
+ {consumerKey:'foo', consumerSecret:'bar', accessorSecret:'nurn', token:'krelm', tokenSecret:'blah'}
+ The accessorSecret property is optional.
+ */
+ completeRequest: function completeRequest(message, accessor) {
+ if (message.method == null) {
+ message.method = "GET";
+ }
+ var map = OAuth.getParameterMap(message.parameters);
+ if (map.oauth_consumer_key == null) {
+ OAuth.setParameter(message, "oauth_consumer_key", accessor.consumerKey || "");
+ }
+ if (map.oauth_token == null && accessor.token != null) {
+ OAuth.setParameter(message, "oauth_token", accessor.token);
+ }
+ if (map.oauth_version == null) {
+ OAuth.setParameter(message, "oauth_version", "1.0");
+ }
+ if (map.oauth_timestamp == null) {
+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
+ }
+ if (map.oauth_nonce == null) {
+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
+ }
+ OAuth.SignatureMethod.sign(message, accessor);
+ }
+,
+ setTimestampAndNonce: function setTimestampAndNonce(message) {
+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
+ }
+,
+ addToURL: function addToURL(url, parameters) {
+ newURL = url;
+ if (parameters != null) {
+ var toAdd = OAuth.formEncode(parameters);
+ if (toAdd.length > 0) {
+ var q = url.indexOf('?');
+ if (q < 0) newURL += '?';
+ else newURL += '&';
+ newURL += toAdd;
+ }
+ }
+ return newURL;
+ }
+,
+ /** Construct the value of the Authorization header for an HTTP request. */
+ getAuthorizationHeader: function getAuthorizationHeader(realm, parameters) {
+ var header = 'OAuth realm="' + OAuth.percentEncode(realm) + '"';
+ var list = OAuth.getParameterList(parameters);
+ for (var p = 0; p < list.length; ++p) {
+ var parameter = list[p];
+ var name = parameter[0];
+ if (name.indexOf("oauth_") == 0) {
+ header += ',' + OAuth.percentEncode(name) + '="' + OAuth.percentEncode(parameter[1]) + '"';
+ }
+ }
+ return header;
+ }
+,
+ /** Correct the time using a parameter from the URL from which the last script was loaded. */
+ correctTimestampFromSrc: function correctTimestampFromSrc(parameterName) {
+ parameterName = parameterName || "oauth_timestamp";
+ var scripts = document.getElementsByTagName('script');
+ if (scripts == null || !scripts.length) return;
+ var src = scripts[scripts.length-1].src;
+ if (!src) return;
+ var q = src.indexOf("?");
+ if (q < 0) return;
+ parameters = OAuth.getParameterMap(OAuth.decodeForm(src.substring(q+1)));
+ var t = parameters[parameterName];
+ if (t == null) return;
+ OAuth.correctTimestamp(t);
+ }
+,
+ /** Generate timestamps starting with the given value. */
+ correctTimestamp: function correctTimestamp(timestamp) {
+ OAuth.timeCorrectionMsec = (timestamp * 1000) - (new Date()).getTime();
+ }
+,
+ /** The difference between the correct time and my clock. */
+ timeCorrectionMsec: 0
+,
+ timestamp: function timestamp() {
+ var t = (new Date()).getTime() + OAuth.timeCorrectionMsec;
+ return Math.floor(t / 1000);
+ }
+,
+ nonce: function nonce(length) {
+ var chars = OAuth.nonce.CHARS;
+ var result = "";
+ for (var i = 0; i < length; ++i) {
+ var rnum = Math.floor(Math.random() * chars.length);
+ result += chars.substring(rnum, rnum+1);
+ }
+ return result;
+ }
+});
+
+OAuth.nonce.CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+
+/** Define a constructor function,
+ without causing trouble to anyone who was using it as a namespace.
+ That is, if parent[name] already existed and had properties,
+ copy those properties into the new constructor.
+ */
+OAuth.declareClass = function declareClass(parent, name, newConstructor) {
+ var previous = parent[name];
+ parent[name] = newConstructor;
+ if (newConstructor != null && previous != null) {
+ for (var key in previous) {
+ if (key != "prototype") {
+ newConstructor[key] = previous[key];
+ }
+ }
+ }
+ return newConstructor;
+}
+
+/** An abstract algorithm for signing messages. */
+OAuth.declareClass(OAuth, "SignatureMethod", function OAuthSignatureMethod(){});
+
+OAuth.setProperties(OAuth.SignatureMethod.prototype, // instance members
+{
+ /** Add a signature to the message. */
+ sign: function sign(message) {
+ var baseString = OAuth.SignatureMethod.getBaseString(message);
+ var signature = this.getSignature(baseString);
+ OAuth.setParameter(message, "oauth_signature", signature);
+ return signature; // just in case someone's interested
+ }
+,
+ /** Set the key string for signing. */
+ initialize: function initialize(name, accessor) {
+ var consumerSecret;
+ if (accessor.accessorSecret != null
+ && name.length > 9
+ && name.substring(name.length-9) == "-Accessor")
+ {
+ consumerSecret = accessor.accessorSecret;
+ } else {
+ consumerSecret = accessor.consumerSecret;
+ }
+ this.key = OAuth.percentEncode(consumerSecret)
+ +"&"+ OAuth.percentEncode(accessor.tokenSecret);
+ }
+});
+
+/* SignatureMethod expects an accessor object to be like this:
+ {tokenSecret: "lakjsdflkj...", consumerSecret: "QOUEWRI..", accessorSecret: "xcmvzc..."}
+ The accessorSecret property is optional.
+ */
+// Class members:
+OAuth.setProperties(OAuth.SignatureMethod, // class members
+{
+ sign: function sign(message, accessor) {
+ var name = OAuth.getParameterMap(message.parameters).oauth_signature_method;
+ if (name == null || name == "") {
+ name = "HMAC-SHA1";
+ OAuth.setParameter(message, "oauth_signature_method", name);
+ }
+ OAuth.SignatureMethod.newMethod(name, accessor).sign(message);
+ }
+,
+ /** Instantiate a SignatureMethod for the given method name. */
+ newMethod: function newMethod(name, accessor) {
+ var impl = OAuth.SignatureMethod.REGISTERED[name];
+ if (impl != null) {
+ var method = new impl();
+ method.initialize(name, accessor);
+ return method;
+ }
+ var err = new Error("signature_method_rejected");
+ var acceptable = "";
+ for (var r in OAuth.SignatureMethod.REGISTERED) {
+ if (acceptable != "") acceptable += '&';
+ acceptable += OAuth.percentEncode(r);
+ }
+ err.oauth_acceptable_signature_methods = acceptable;
+ throw err;
+ }
+,
+ /** A map from signature method name to constructor. */
+ REGISTERED : {}
+,
+ /** Subsequently, the given constructor will be used for the named methods.
+ The constructor will be called with no parameters.
+ The resulting object should usually implement getSignature(baseString).
+ You can easily define such a constructor by calling makeSubclass, below.
+ */
+ registerMethodClass: function registerMethodClass(names, classConstructor) {
+ for (var n = 0; n < names.length; ++n) {
+ OAuth.SignatureMethod.REGISTERED[names[n]] = classConstructor;
+ }
+ }
+,
+ /** Create a subclass of OAuth.SignatureMethod, with the given getSignature function. */
+ makeSubclass: function makeSubclass(getSignatureFunction) {
+ var superClass = OAuth.SignatureMethod;
+ var subClass = function() {
+ superClass.call(this);
+ };
+ subClass.prototype = new superClass();
+ // Delete instance variables from prototype:
+ // delete subclass.prototype... There aren't any.
+ subClass.prototype.getSignature = getSignatureFunction;
+ subClass.prototype.constructor = subClass;
+ return subClass;
+ }
+,
+ getBaseString: function getBaseString(message) {
+ var URL = message.action;
+ var q = URL.indexOf('?');
+ var parameters;
+ if (q < 0) {
+ parameters = message.parameters;
+ } else {
+ // Combine the URL query string with the other parameters:
+ parameters = OAuth.decodeForm(URL.substring(q + 1));
+ var toAdd = OAuth.getParameterList(message.parameters);
+ for (var a = 0; a < toAdd.length; ++a) {
+ parameters.push(toAdd[a]);
+ }
+ }
+ return OAuth.percentEncode(message.method.toUpperCase())
+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeUrl(URL))
+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeParameters(parameters));
+ }
+,
+ normalizeUrl: function normalizeUrl(url) {
+ var uri = OAuth.SignatureMethod.parseUri(url);
+ var scheme = uri.protocol.toLowerCase();
+ var authority = uri.authority.toLowerCase();
+ var dropPort = (scheme == "http" && uri.port == 80)
+ || (scheme == "https" && uri.port == 443);
+ if (dropPort) {
+ // find the last : in the authority
+ var index = authority.lastIndexOf(":");
+ if (index >= 0) {
+ authority = authority.substring(0, index);
+ }
+ }
+ var path = uri.path;
+ if (!path) {
+ path = "/"; // conforms to RFC 2616 section 3.2.2
+ }
+ // we know that there is no query and no fragment here.
+ return scheme + "://" + authority + path;
+ }
+,
+ parseUri: function parseUri (str) {
+ /* This function was adapted from parseUri 1.2.1
+ http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js
+ */
+ var o = {key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
+ parser: {strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/ }};
+ var m = o.parser.strict.exec(str);
+ var uri = {};
+ var i = 14;
+ while (i--) uri[o.key[i]] = m[i] || "";
+ return uri;
+ }
+,
+ normalizeParameters: function normalizeParameters(parameters) {
+ if (parameters == null) {
+ return "";
+ }
+ var list = OAuth.getParameterList(parameters);
+ var sortable = [];
+ for (var p = 0; p < list.length; ++p) {
+ var nvp = list[p];
+ if (nvp[0] != "oauth_signature") {
+ sortable.push([ OAuth.percentEncode(nvp[0])
+ + " " // because it comes before any character that can appear in a percentEncoded string.
+ + OAuth.percentEncode(nvp[1])
+ , nvp]);
+ }
+ }
+ sortable.sort(function(a,b) {
+ if (a[0] < b[0]) return -1;
+ if (a[0] > b[0]) return 1;
+ return 0;
+ });
+ var sorted = [];
+ for (var s = 0; s < sortable.length; ++s) {
+ sorted.push(sortable[s][1]);
+ }
+ return OAuth.formEncode(sorted);
+ }
+});
+
+OAuth.SignatureMethod.registerMethodClass(["PLAINTEXT", "PLAINTEXT-Accessor"],
+ OAuth.SignatureMethod.makeSubclass(
+ function getSignature(baseString) {
+ return this.key;
+ }
+ ));
+
+OAuth.SignatureMethod.registerMethodClass(["HMAC-SHA1", "HMAC-SHA1-Accessor"],
+ OAuth.SignatureMethod.makeSubclass(
+ function getSignature(baseString) {
+ b64pad = '=';
+ var signature = b64_hmac_sha1(this.key, baseString);
+ return signature;
+ }
+ ));
+
+try {
+ OAuth.correctTimestampFromSrc();
+} catch(e) {
+}
diff --git a/src/js/options.js b/src/js/options.js
new file mode 100644
index 0000000..9d61498
--- /dev/null
+++ b/src/js/options.js
@@ -0,0 +1,181 @@
+// Copyright (c) 2009 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 bg = chrome.extension.getBackgroundPage();
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+function isHighVersion() {
+ var version = navigator.userAgent.match(/Chrome\/(\d+)/)[1];
+ return version > 9;
+}
+
+function init() {
+ i18nReplace('optionTitle', 'options');
+ i18nReplace('saveAndClose', 'save_and_close');
+ i18nReplace('screenshootQualitySetting', 'quality_setting');
+ i18nReplace('lossyScreenShot', 'lossy');
+ i18nReplace('losslessScreenShot', 'lossless');
+ i18nReplace('shorcutSetting', 'shortcut_setting');
+ i18nReplace('settingShortcutText', 'shortcutsetting_text');
+ if (isHighVersion()) {
+ $('lossyScreenShot').innerText += ' (JPEG)';
+ $('losslessScreenShot').innerText += ' (PNG)';
+ }
+ $('saveAndClose').addEventListener('click', saveAndClose);
+ initScreenCaptureQuality();
+ HotKeySetting.setup();
+}
+
+function save() {
+ localStorage.screenshootQuality =
+ $('lossy').checked ? 'jpeg' : '' ||
+ $('lossless').checked ? 'png' : '';
+
+ return HotKeySetting.save();
+}
+
+function saveAndClose() {
+ if (save())
+ chrome.tabs.getSelected(null, function(tab) {
+ chrome.tabs.remove(tab.id);
+ });
+}
+
+function initScreenCaptureQuality() {
+ $('lossy').checked = localStorage.screenshootQuality == 'jpeg';
+ $('lossless').checked = localStorage.screenshootQuality == 'png';
+}
+
+function i18nReplace(id, name) {
+ return $(id).innerText = chrome.i18n.getMessage(name);
+}
+
+const CURRENT_LOCALE = chrome.i18n.getMessage('@@ui_locale');
+if (CURRENT_LOCALE != 'zh_CN') {
+ UI.addStyleSheet('./i18n_styles/en_options.css');
+}
+
+var HotKeySetting = (function() {
+ const CHAR_CODE_OF_AT = 64;
+ const CHAR_CODE_OF_A = 65;
+ const CHAR_CODE_OF_Z = 90;
+ var hotKeySelection = null;
+
+ var hotkey = {
+ setup: function() {
+ hotKeySelection = document.querySelectorAll('#hot-key-setting select');
+ // i18n.
+ $('area-capture-text').innerText =
+ chrome.i18n.getMessage('capture_area');
+ $('viewport-capture-text').innerText =
+ chrome.i18n.getMessage('capture_window');
+ $('full-page-capture-text').innerText =
+ chrome.i18n.getMessage('capture_webpage');
+ //$('screen-capture-text').innerText = chrome.i18n.getMessage('capture_screen');
+
+ for (var i = 0; i < hotKeySelection.length; i++) {
+ hotKeySelection[i].add(new Option('--', '@'));
+ for (var j = CHAR_CODE_OF_A; j <= CHAR_CODE_OF_Z; j++) {
+ var value = String.fromCharCode(j);
+ var option = new Option(value, value);
+ hotKeySelection[i].add(option);
+ }
+ }
+
+ $('area-capture-hot-key').selectedIndex =
+ HotKey.getCharCode('area') - CHAR_CODE_OF_AT;
+ $('viewport-capture-hot-key').selectedIndex =
+ HotKey.getCharCode('viewport') - CHAR_CODE_OF_AT;
+ $('full-page-capture-hot-key').selectedIndex =
+ HotKey.getCharCode('fullpage') - CHAR_CODE_OF_AT;
+ $('screen-capture-hot-key').selectedIndex =
+ HotKey.getCharCode('screen') - CHAR_CODE_OF_AT;
+
+ $('settingShortcut').addEventListener('click', function() {
+ hotkey.setState(this.checked);
+ }, false);
+
+ hotkey.setState(HotKey.isEnabled());
+ },
+
+ validate: function() {
+ var hotKeyLength =
+ Array.prototype.filter.call(hotKeySelection,
+ function (element) {
+ return element.value != '@'
+ }
+ ).length;
+ if (hotKeyLength != 0) {
+ var validateMap = {};
+ validateMap[hotKeySelection[0].value] = true;
+ validateMap[hotKeySelection[1].value] = true;
+ validateMap[hotKeySelection[2].value] = true;
+ if (hotKeyLength > 3 && hotKeySelection[3].value != '@') {
+ hotKeyLength -= 1;
+ }
+
+ if (Object.keys(validateMap).length < hotKeyLength) {
+ ErrorInfo.show('hot_key_conflict');
+ return false;
+ }
+ }
+ ErrorInfo.hide();
+ return true;
+ },
+
+ save: function() {
+ var result = true;
+ if ($('settingShortcut').checked) {
+ if (this.validate()) {
+ HotKey.enable();
+ HotKey.set('area', $('area-capture-hot-key').value);
+ HotKey.set('viewport', $('viewport-capture-hot-key').value);
+ HotKey.set('fullpage', $('full-page-capture-hot-key').value);
+ } else {
+ result = false;
+ }
+ } else {
+ HotKey.disable(bg);
+ }
+ return result;
+ },
+
+ setState: function(enabled) {
+ $('settingShortcut').checked = enabled;
+ UI.setStyle($('hot-key-setting'), 'color', enabled ? '' : '#6d6d6d');
+ for (var i = 0; i < hotKeySelection.length; i++) {
+ hotKeySelection[i].disabled = !enabled;
+ }
+ ErrorInfo.hide();
+ },
+
+ focusScreenCapture: function() {
+ $('screen-capture-hot-key').focus();
+ }
+ };
+ return hotkey;
+})();
+
+var ErrorInfo = (function() {
+ return {
+ show: function(msgKey) {
+ var infoWrapper = $('error-info');
+ var msg = chrome.i18n.getMessage(msgKey);
+ infoWrapper.innerText = msg;
+ UI.show(infoWrapper);
+ },
+
+ hide: function() {
+ var infoWrapper = $('error-info');
+ if (infoWrapper) {
+ UI.hide(infoWrapper);
+ }
+ }
+ };
+})();
+
+document.addEventListener('DOMContentLoaded', init);
diff --git a/src/js/page.js b/src/js/page.js
new file mode 100644
index 0000000..2c5702d
--- /dev/null
+++ b/src/js/page.js
@@ -0,0 +1,907 @@
+// Copyright (c) 2010 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 page = {
+ startX: 150,
+ startY: 150,
+ endX: 400,
+ endY: 300,
+ moveX: 0,
+ moveY: 0,
+ pageWidth: 0,
+ pageHeight: 0,
+ visibleWidth: 0,
+ visibleHeight: 0,
+ dragging: false,
+ moving: false,
+ resizing: false,
+ isMouseDown: false,
+ scrollXCount: 0,
+ scrollYCount: 0,
+ scrollX: 0,
+ scrollY: 0,
+ captureWidth: 0,
+ captureHeight: 0,
+ isSelectionAreaTurnOn: false,
+ fixedElements_ : [],
+ marginTop: 0,
+ marginLeft: 0,
+ modifiedBottomRightFixedElements: [],
+ originalViewPortWidth: document.documentElement.clientWidth,
+ defaultScrollBarWidth: 17, // Default scroll bar width on windows platform.
+
+ hookBodyScrollValue: function(needHook) {
+ document.documentElement.setAttribute(
+ "__screen_capture_need_hook_scroll_value__", needHook);
+ var event = document.createEvent('Event');
+ event.initEvent('__screen_capture_check_hook_status_event__', true, true);
+ document.documentElement.dispatchEvent(event);
+ },
+
+ /**
+ * Determine if the page scrolled to bottom or right.
+ */
+ isScrollToPageEnd: function(coordinate) {
+ var body = document.body;
+ var docElement = document.documentElement;
+ if (coordinate == 'x')
+ return docElement.clientWidth + body.scrollLeft == body.scrollWidth;
+ else if (coordinate == 'y')
+ return docElement.clientHeight + body.scrollTop == body.scrollHeight;
+ },
+
+ /**
+ * Detect if the view port is located to the corner of page.
+ */
+ detectPagePosition: function() {
+ var body = document.body;
+ var pageScrollTop = body.scrollTop;
+ var pageScrollLeft = body.scrollLeft;
+ if (pageScrollTop == 0 && pageScrollLeft == 0) {
+ return 'top_left';
+ } else if (pageScrollTop == 0 && this.isScrollToPageEnd('x')) {
+ return 'top_right';
+ } else if (this.isScrollToPageEnd('y') && pageScrollLeft == 0) {
+ return 'bottom_left';
+ } else if (this.isScrollToPageEnd('y') && this.isScrollToPageEnd('x')) {
+ return 'bottom_right';
+ }
+ return null;
+ },
+
+ /**
+ * Detect fixed-positioned element's position in the view port.
+ * @param {Element} elem
+ * @return {String|Object} Return position of the element in the view port:
+ * top_left, top_right, bottom_left, bottom_right, or null.
+ */
+ detectCapturePositionOfFixedElement: function(elem) {
+ var docElement = document.documentElement;
+ var viewPortWidth = docElement.clientWidth;
+ var viewPortHeight = docElement.clientHeight;
+ var offsetWidth = elem.offsetWidth;
+ var offsetHeight = elem.offsetHeight;
+ var offsetTop = elem.offsetTop;
+ var offsetLeft = elem.offsetLeft;
+ var result = [];
+
+ // Compare distance between element and the edge of view port to determine
+ // the capture position of element.
+ if (offsetTop <= viewPortHeight - offsetTop - offsetHeight) {
+ result.push('top');
+ } else if (offsetTop < viewPortHeight) {
+ result.push('bottom');
+ }
+ if (offsetLeft <= viewPortWidth - offsetLeft - offsetWidth) {
+ result.push('left');
+ } else if (offsetLeft < viewPortWidth) {
+ result.push('right');
+ }
+
+ // If the element is out of view port, then ignore.
+ if (result.length != 2)
+ return null;
+ return result.join('_');
+ },
+
+ restoreFixedElements: function() {
+ this.fixedElements_.forEach(function(element) {
+ element[1].style.visibility = 'visible';
+ });
+ this.fixedElements_ = [];
+ },
+
+ /**
+ * Iterate DOM tree and cache visible fixed-position elements.
+ */
+ cacheVisibleFixedPositionedElements: function() {
+ var nodeIterator = document.createNodeIterator(
+ document.documentElement,
+ NodeFilter.SHOW_ELEMENT,
+ null,
+ false
+ );
+ var currentNode;
+ while (currentNode = nodeIterator.nextNode()) {
+ var nodeComputedStyle =
+ document.defaultView.getComputedStyle(currentNode, "");
+ // Skip nodes which don't have computeStyle or are invisible.
+ if (!nodeComputedStyle)
+ continue;
+ if (nodeComputedStyle.position == "fixed" &&
+ nodeComputedStyle.display != 'none' &&
+ nodeComputedStyle.visibility != 'hidden') {
+ var position =
+ this.detectCapturePositionOfFixedElement(currentNode);
+ if (position)
+ this.fixedElements_.push([position, currentNode]);
+ }
+ }
+ },
+
+ // Handle fixed-position elements for capture.
+ handleFixedElements: function(capturePosition) {
+ var docElement = document.documentElement;
+ var body = document.body;
+
+ // If page has no scroll bar, then return directly.
+ if (docElement.clientHeight == body.scrollHeight &&
+ docElement.clientWidth == body.scrollWidth)
+ return;
+
+ if (!this.fixedElements_.length) {
+ this.cacheVisibleFixedPositionedElements();
+ }
+
+ this.fixedElements_.forEach(function(element) {
+ if (element[0] == capturePosition)
+ element[1].style.visibility = 'visible';
+ else
+ element[1].style.visibility = 'hidden';
+ });
+ },
+
+ handleSecondToLastCapture: function() {
+ var docElement = document.documentElement;
+ var body = document.body;
+ var bottomPositionElements = [];
+ var rightPositionElements = [];
+ var that = this;
+ this.fixedElements_.forEach(function(element) {
+ var position = element[0];
+ if (position == 'bottom_left' || position == 'bottom_right') {
+ bottomPositionElements.push(element[1]);
+ } else if (position == 'bottom_right' || position == 'top_right') {
+ rightPositionElements.push(element[1]);
+ }
+ });
+
+ // Determine if the current capture is last but one.
+ var remainingCaptureHeight = body.scrollHeight - docElement.clientHeight -
+ body.scrollTop;
+ if (remainingCaptureHeight > 0 &&
+ remainingCaptureHeight < docElement.clientHeight) {
+ bottomPositionElements.forEach(function(element) {
+ if (element.offsetHeight > remainingCaptureHeight) {
+ element.style.visibility = 'visible';
+ var originalBottom = window.getComputedStyle(element).bottom;
+ that.modifiedBottomRightFixedElements.push(
+ ['bottom', element, originalBottom]);
+ element.style.bottom = -remainingCaptureHeight + 'px';
+ }
+ });
+ }
+
+ var remainingCaptureWidth = body.scrollWidth - docElement.clientWidth -
+ body.scrollLeft;
+ if (remainingCaptureWidth > 0 &&
+ remainingCaptureWidth < docElement.clientWidth) {
+ rightPositionElements.forEach(function(element) {
+ if (element.offsetWidth > remainingCaptureWidth) {
+ element.style.visibility = 'visible';
+ var originalRight = window.getComputedStyle(element).right;
+ that.modifiedBottomRightFixedElements.push(
+ ['right', element, originalRight]);
+ element.style.right = -remainingCaptureWidth + 'px';
+ }
+ });
+ }
+ },
+
+ restoreBottomRightOfFixedPositionElements: function() {
+ this.modifiedBottomRightFixedElements.forEach(function(data) {
+ var property = data[0];
+ var element = data[1];
+ var originalValue = data[2];
+ element.style[property] = originalValue;
+ });
+ this.modifiedBottomRightFixedElements = [];
+ },
+
+ hideAllFixedPositionedElements: function() {
+ this.fixedElements_.forEach(function(element) {
+ element[1].style.visibility = 'hidden';
+ });
+ },
+
+ hasScrollBar: function(axis) {
+ var body = document.body;
+ var docElement = document.documentElement;
+ if (axis == 'x') {
+ if (window.getComputedStyle(body).overflowX == 'scroll')
+ return true;
+ return Math.abs(body.scrollWidth - docElement.clientWidth) >=
+ page.defaultScrollBarWidth;
+ } else if (axis == 'y') {
+ if (window.getComputedStyle(body).overflowY == 'scroll')
+ return true;
+ return Math.abs(body.scrollHeight - docElement.clientHeight) >=
+ page.defaultScrollBarWidth;
+ }
+ },
+
+ getOriginalViewPortWidth: function() {
+ chrome.extension.sendMessage({ msg: 'original_view_port_width'},
+ function(originalViewPortWidth) {
+ if (originalViewPortWidth) {
+ page.originalViewPortWidth = page.hasScrollBar('y') ?
+ originalViewPortWidth - page.defaultScrollBarWidth : originalViewPortWidth;
+ } else {
+ page.originalViewPortWidth = document.documentElement.clientWidth;
+ }
+ });
+ },
+
+ calculateSizeAfterZooming: function(originalSize) {
+ var originalViewPortWidth = page.originalViewPortWidth;
+ var currentViewPortWidth = document.documentElement.clientWidth;
+ if (originalViewPortWidth == currentViewPortWidth)
+ return originalSize;
+ return Math.round(
+ originalViewPortWidth * originalSize / currentViewPortWidth);
+ },
+
+ getZoomLevel: function() {
+ return page.originalViewPortWidth / document.documentElement.clientWidth;
+ },
+
+ handleRightFloatBoxInGmail: function() {
+ var mainframe = document.getElementById('canvas_frame');
+ var boxContainer = document.querySelector('body > .dw');
+ var fBody = mainframe.contentDocument.body;
+ if (fBody.clientHeight + fBody.scrollTop == fBody.scrollHeight) {
+ boxContainer.style.display = 'block';
+ } else {
+ boxContainer.style.display = 'none';
+ }
+ },
+
+ getViewPortSize: function() {
+ var result = {
+ width: document.documentElement.clientWidth,
+ height: document.documentElement.clientHeight
+ };
+
+ if (document.compatMode == 'BackCompat') {
+ result.width = document.body.clientWidth;
+ result.height = document.body.clientHeight;
+ }
+
+ return result;
+ },
+
+ /**
+ * Check if the page is only made of invisible embed elements.
+ */
+ checkPageIsOnlyEmbedElement: function() {
+ var bodyNode = document.body.children;
+ var isOnlyEmbed = false;
+ for (var i = 0; i < bodyNode.length; i++) {
+ var tagName = bodyNode[i].tagName;
+ if (tagName == 'OBJECT' || tagName == 'EMBED' || tagName == 'VIDEO' ||
+ tagName == 'SCRIPT' || tagName == 'LINK') {
+ isOnlyEmbed = true;
+ } else if (bodyNode[i].style.display != 'none'){
+ isOnlyEmbed = false;
+ break;
+ }
+ }
+ return isOnlyEmbed;
+ },
+
+ isGMailPage: function(){
+ var hostName = window.location.hostname;
+ if (hostName == 'mail.google.com' &&
+ document.getElementById('canvas_frame')) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Receive messages from background page, and then decide what to do next
+ */
+ addMessageListener: function() {
+ chrome.extension.onMessage.addListener(function(request, sender, response) {
+ if (page.isSelectionAreaTurnOn) {
+ page.removeSelectionArea();
+ }
+ switch (request.msg) {
+ case 'capture_window': response(page.getWindowSize()); break;
+ case 'show_selection_area': page.showSelectionArea(); break;
+ case 'scroll_init': // Capture whole page.
+ response(page.scrollInit(0, 0, document.body.scrollWidth,
+ document.body.scrollHeight, 'captureWhole'));
+ break;
+ case 'scroll_next':
+ page.visibleWidth = request.visibleWidth;
+ page.visibleHeight = request.visibleHeight;
+ response(page.scrollNext());
+ break;
+ case 'capture_selected':
+ response(page.scrollInit(
+ page.startX, page.startY,
+ page.calculateSizeAfterZooming(page.endX - page.startX),
+ page.calculateSizeAfterZooming(page.endY - page.startY),
+ 'captureSelected'));
+ break;
+ }
+ });
+ },
+
+ /**
+ * Send Message to background page
+ */
+ sendMessage: function(message) {
+ chrome.extension.sendMessage(message);
+ },
+
+ /**
+ * Initialize scrollbar position, and get the data browser
+ */
+ scrollInit: function(startX, startY, canvasWidth, canvasHeight, type) {
+ this.hookBodyScrollValue(true);
+ page.captureHeight = canvasHeight;
+ page.captureWidth = canvasWidth;
+ var docWidth = document.body.scrollWidth;
+ var docHeight = document.body.scrollHeight;
+ window.scrollTo(startX, startY);
+
+ this.handleFixedElements('top_left');
+ this.handleSecondToLastCapture();
+
+ if (page.isGMailPage() && type == 'captureWhole') {
+ var frame = document.getElementById('canvas_frame');
+ docHeight = page.captureHeight = canvasHeight =
+ frame.contentDocument.height;
+ docWidth = page.captureWidth = canvasWidth = frame.contentDocument.width;
+ frame.contentDocument.body.scrollTop = 0;
+ frame.contentDocument.body.scrollLeft = 0;
+ page.handleRightFloatBoxInGmail();
+ }
+ page.scrollXCount = 0;
+ page.scrollYCount = 1;
+ page.scrollX = window.scrollX; // document.body.scrollLeft
+ page.scrollY = window.scrollY;
+ var viewPortSize = page.getViewPortSize();
+ return {
+ 'msg': 'scroll_init_done',
+ 'startX': page.calculateSizeAfterZooming(startX),
+ 'startY': page.calculateSizeAfterZooming(startY),
+ 'scrollX': window.scrollX,
+ 'scrollY': window.scrollY,
+ 'docHeight': docHeight,
+ 'docWidth': docWidth,
+ 'visibleWidth': viewPortSize.width,
+ 'visibleHeight': viewPortSize.height,
+ 'canvasWidth': canvasWidth,
+ 'canvasHeight': canvasHeight,
+ 'scrollXCount': 0,
+ 'scrollYCount': 0,
+ 'zoom': page.getZoomLevel()
+ };
+ },
+
+ /**
+ * Calculate the next position of the scrollbar
+ */
+ scrollNext: function() {
+ if (page.scrollYCount * page.visibleWidth >= page.captureWidth) {
+ page.scrollXCount++;
+ page.scrollYCount = 0;
+ }
+ if (page.scrollXCount * page.visibleHeight < page.captureHeight) {
+ this.restoreBottomRightOfFixedPositionElements();
+ var viewPortSize = page.getViewPortSize();
+ window.scrollTo(
+ page.scrollYCount * viewPortSize.width + page.scrollX,
+ page.scrollXCount * viewPortSize.height + page.scrollY);
+
+ var pagePosition = this.detectPagePosition();
+ if (pagePosition) {
+ this.handleFixedElements(pagePosition);
+ } else {
+ this.hideAllFixedPositionedElements();
+ }
+ this.handleSecondToLastCapture();
+
+ if (page.isGMailPage()) {
+ var frame = document.getElementById('canvas_frame');
+ frame.contentDocument.body.scrollLeft =
+ page.scrollYCount * viewPortSize.width;
+ frame.contentDocument.body.scrollTop =
+ page.scrollXCount * viewPortSize.height;
+ page.handleRightFloatBoxInGmail();
+ }
+ var x = page.scrollXCount;
+ var y = page.scrollYCount;
+ page.scrollYCount++;
+ return { msg: 'scroll_next_done',scrollXCount: x, scrollYCount: y };
+ } else {
+ window.scrollTo(page.startX, page.startY);
+ this.restoreFixedElements();
+ this.hookBodyScrollValue(false);
+ return {'msg': 'scroll_finished'};
+ }
+ },
+
+ /**
+ * Show the selection Area
+ */
+ showSelectionArea: function() {
+ page.createFloatLayer();
+ setTimeout(page.createSelectionArea, 100);
+ },
+
+ getWindowSize: function() {
+ var docWidth = document.body.clientWidth;
+ var docHeight = document.body.clientHeight;
+ if (page.isGMailPage()) {
+ var frame = document.getElementById('canvas_frame');
+ docHeight = frame.contentDocument.height;
+ docWidth = frame.contentDocument.width;
+ }
+ return {'msg':'capture_window',
+ 'docWidth': docWidth,
+ 'docHeight': docHeight};
+ },
+
+ getSelectionSize: function() {
+ page.removeSelectionArea();
+ setTimeout(function() {
+ page.sendMessage({
+ 'msg': 'capture_selected',
+ 'x': page.startX,
+ 'y': page.startY,
+ 'width': page.endX - page.startX,
+ 'height': page.endY - page.startY,
+ 'visibleWidth': document.documentElement.clientWidth,
+ 'visibleHeight': document.documentElement.clientHeight,
+ 'docWidth': document.body.clientWidth,
+ 'docHeight': document.body.clientHeight
+ })}, 100);
+ },
+
+ /**
+ * Create a float layer on the webpage
+ */
+ createFloatLayer: function() {
+ page.createDiv(document.body, 'sc_drag_area_protector');
+ },
+
+ matchMarginValue: function(str) {
+ return str.match(/\d+/);
+ },
+
+ /**
+ * Load the screenshot area interface
+ */
+ createSelectionArea: function() {
+ var areaProtector = $('sc_drag_area_protector');
+ var zoom = page.getZoomLevel();
+ var bodyStyle = window.getComputedStyle(document.body, null);
+ if ('relative' == bodyStyle['position']) {
+ page.marginTop = page.matchMarginValue(bodyStyle['marginTop']);
+ page.marginLeft = page.matchMarginValue(bodyStyle['marginLeft']);
+ areaProtector.style.top = - parseInt(page.marginTop) + 'px';
+ areaProtector.style.left = - parseInt(page.marginLeft) + 'px';
+ }
+ areaProtector.style.width =
+ Math.round((document.body.clientWidth + parseInt(page.marginLeft)) / zoom) + 'px';
+ areaProtector.style.height =
+ Math.round((document.body.clientHeight + parseInt(page.marginTop)) / zoom) + 'px';
+ areaProtector.onclick = function() {
+ event.stopPropagation();
+ return false;
+ };
+
+ // Create elements for area capture.
+ page.createDiv(areaProtector, 'sc_drag_shadow_top');
+ page.createDiv(areaProtector, 'sc_drag_shadow_bottom');
+ page.createDiv(areaProtector, 'sc_drag_shadow_left');
+ page.createDiv(areaProtector, 'sc_drag_shadow_right');
+
+ var areaElement = page.createDiv(areaProtector, 'sc_drag_area');
+ page.createDiv(areaElement, 'sc_drag_container');
+ page.createDiv(areaElement, 'sc_drag_size');
+
+ // Add event listener for 'cancel' and 'capture' button.
+ var cancel = page.createDiv(areaElement, 'sc_drag_cancel');
+ cancel.addEventListener('mousedown', function () {
+ // Remove area capture containers and event listeners.
+ page.removeSelectionArea();
+ }, true);
+ cancel.innerHTML = chrome.i18n.getMessage("cancel");
+
+ var crop = page.createDiv(areaElement, 'sc_drag_crop');
+ crop.addEventListener('mousedown', function() {
+ page.removeSelectionArea();
+ page.sendMessage({msg: 'capture_selected'});
+ }, false);
+ crop.innerHTML = chrome.i18n.getMessage('ok');
+
+ page.createDiv(areaElement, 'sc_drag_north_west');
+ page.createDiv(areaElement, 'sc_drag_north_east');
+ page.createDiv(areaElement, 'sc_drag_south_east');
+ page.createDiv(areaElement, 'sc_drag_south_west');
+
+ areaProtector.addEventListener('mousedown', page.onMouseDown, false);
+ document.addEventListener('mousemove', page.onMouseMove, false);
+ document.addEventListener('mouseup', page.onMouseUp, false);
+ $('sc_drag_container').addEventListener('dblclick', function() {
+ page.removeSelectionArea();
+ page.sendMessage({msg: 'capture_selected'});
+ }, false);
+
+ page.pageHeight = $('sc_drag_area_protector').clientHeight;
+ page.pageWidth = $('sc_drag_area_protector').clientWidth;
+
+ var areaElement = $('sc_drag_area');
+ areaElement.style.left = page.getElementLeft(areaElement) + 'px';
+ areaElement.style.top = page.getElementTop(areaElement) + 'px';
+
+ page.startX = page.getElementLeft(areaElement);
+ page.startY = page.getElementTop(areaElement);
+ page.endX = page.getElementLeft(areaElement) + 250;
+ page.endY = page.getElementTop(areaElement) + 150;
+
+ areaElement.style.width = '250px';
+ areaElement.style.height = '150px';
+ page.isSelectionAreaTurnOn = true;
+ page.updateShadow(areaElement);
+ page.updateSize();
+ },
+
+ getElementLeft: function(obj) {
+ return (document.body.scrollLeft +
+ (document.documentElement.clientWidth -
+ obj.offsetWidth) / 2);
+ },
+
+ getElementTop: function(obj) {
+ return (document.body.scrollTop +
+ (document.documentElement.clientHeight - 200 -
+ obj.offsetHeight) / 2);
+ },
+
+ /**
+ * Init selection area due to the position of the mouse when mouse down
+ */
+ onMouseDown: function() {
+ if (event.button != 2) {
+ var element = event.target;
+
+ if (element) {
+ var elementName = element.tagName;
+ if (elementName && document) {
+ page.isMouseDown = true;
+
+ var areaElement = $('sc_drag_area');
+ var xPosition = event.pageX;
+ var yPosition = event.pageY;
+
+ if (areaElement) {
+ if (element == $('sc_drag_container')) {
+ page.moving = true;
+ page.moveX = xPosition - areaElement.offsetLeft;
+ page.moveY = yPosition - areaElement.offsetTop;
+ } else if (element == $('sc_drag_north_east')) {
+ page.resizing = true;
+ page.startX = areaElement.offsetLeft;
+ page.startY = areaElement.offsetTop + areaElement.clientHeight;
+ } else if (element == $('sc_drag_north_west')) {
+ page.resizing = true;
+ page.startX = areaElement.offsetLeft + areaElement.clientWidth;
+ page.startY = areaElement.offsetTop + areaElement.clientHeight;
+ } else if (element == $('sc_drag_south_east')) {
+ page.resizing = true;
+ page.startX = areaElement.offsetLeft;
+ page.startY = areaElement.offsetTop;
+ } else if (element == $('sc_drag_south_west')) {
+ page.resizing = true;
+ page.startX = areaElement.offsetLeft + areaElement.clientWidth;
+ page.startY = areaElement.offsetTop;
+ } else {
+ page.dragging = true;
+ page.endX = 0;
+ page.endY = 0;
+ page.endX = page.startX = xPosition;
+ page.endY = page.startY = yPosition;
+ }
+ }
+ event.preventDefault();
+ }
+ }
+ }
+ },
+
+ /**
+ * Change selection area position when mouse moved
+ */
+ onMouseMove: function() {
+ var element = event.target;
+ if (element && page.isMouseDown) {
+ var areaElement = $('sc_drag_area');
+ if (areaElement) {
+ var xPosition = event.pageX;
+ var yPosition = event.pageY;
+ if (page.dragging || page.resizing) {
+ var width = 0;
+ var height = 0;
+ var zoom = page.getZoomLevel();
+ var viewWidth = Math.round(document.body.clientWidth / zoom);
+ var viewHeight = Math.round(document.body.clientHeight / zoom);
+ if (xPosition > viewWidth) {
+ xPosition = viewWidth;
+ } else if (xPosition < 0) {
+ xPosition = 0;
+ }
+ if (yPosition > viewHeight) {
+ yPosition = viewHeight;
+ } else if (yPosition < 0) {
+ yPosition = 0;
+ }
+ page.endX = xPosition;
+ page.endY = yPosition;
+ if (page.startX > page.endX) {
+ width = page.startX - page.endX;
+ areaElement.style.left = xPosition + 'px';
+ } else {
+ width = page.endX - page.startX;
+ areaElement.style.left = page.startX + 'px';
+ }
+ if (page.startY > page.endY) {
+ height = page.startY - page.endY;
+ areaElement.style.top = page.endY + 'px';
+ } else {
+ height = page.endY - page.startY;
+ areaElement.style.top = page.startY + 'px';
+ }
+ areaElement.style.height = height + 'px';
+ areaElement.style.width = width + 'px';
+ if (window.innerWidth < xPosition) {
+ document.body.scrollLeft = xPosition - window.innerWidth;
+ }
+ if (document.body.scrollTop + window.innerHeight < yPosition + 25) {
+ document.body.scrollTop = yPosition - window.innerHeight + 25;
+ }
+ if (yPosition < document.body.scrollTop) {
+ document.body.scrollTop -= 25;
+ }
+ } else if (page.moving) {
+ var newXPosition = xPosition - page.moveX;
+ var newYPosition = yPosition - page.moveY;
+ if (newXPosition < 0) {
+ newXPosition = 0;
+ } else if (newXPosition + areaElement.clientWidth > page.pageWidth) {
+ newXPosition = page.pageWidth - areaElement.clientWidth;
+ }
+ if (newYPosition < 0) {
+ newYPosition = 0;
+ } else if (newYPosition + areaElement.clientHeight >
+ page.pageHeight) {
+ newYPosition = page.pageHeight - areaElement.clientHeight;
+ }
+
+ areaElement.style.left = newXPosition + 'px';
+ areaElement.style.top = newYPosition + 'px';
+ page.endX = newXPosition + areaElement.clientWidth;
+ page.startX = newXPosition;
+ page.endY = newYPosition + areaElement.clientHeight;
+ page.startY = newYPosition;
+
+ }
+ var crop = document.getElementById('sc_drag_crop');
+ var cancel = document.getElementById('sc_drag_cancel');
+ if (event.pageY + 25 > document.body.clientHeight) {
+ crop.style.bottom = 0;
+ cancel.style.bottom = 0
+ } else {
+ crop.style.bottom = '-25px';
+ cancel.style.bottom = '-25px';
+ }
+
+ var dragSizeContainer = document.getElementById('sc_drag_size');
+ if (event.pageY < 18) {
+ dragSizeContainer.style.top = 0;
+ } else {
+ dragSizeContainer.style.top = '-18px';
+ }
+ page.updateShadow(areaElement);
+ page.updateSize();
+
+ }
+ }
+ },
+
+ /**
+ * Fix the selection area position when mouse up
+ */
+ onMouseUp: function() {
+ page.isMouseDown = false;
+ if (event.button != 2) {
+ page.resizing = false;
+ page.dragging = false;
+ page.moving = false;
+ page.moveX = 0;
+ page.moveY = 0;
+ var temp;
+ if (page.endX < page.startX) {
+ temp = page.endX;
+ page.endX = page.startX;
+ page.startX = temp;
+ }
+ if (page.endY < page.startY) {
+ temp = page.endY;
+ page.endY = page.startY;
+ page.startY = temp;
+ }
+ }
+ },
+
+ /**
+ * Update the location of the shadow layer
+ */
+ updateShadow: function(areaElement) {
+ $('sc_drag_shadow_top').style.height =
+ parseInt(areaElement.style.top) + 'px';
+ $('sc_drag_shadow_top').style.width = (parseInt(areaElement.style.left) +
+ parseInt(areaElement.style.width) + 1) + 'px';
+ $('sc_drag_shadow_left').style.height =
+ (page.pageHeight - parseInt(areaElement.style.top)) + 'px';
+ $('sc_drag_shadow_left').style.width =
+ parseInt(areaElement.style.left) + 'px';
+
+ var height = (parseInt(areaElement.style.top) +
+ parseInt(areaElement.style.height) + 1);
+ height = (height < 0) ? 0 : height;
+ var width = (page.pageWidth) - 1 - (parseInt(areaElement.style.left) +
+ parseInt(areaElement.style.width));
+ width = (width < 0) ? 0 : width;
+ $('sc_drag_shadow_right').style.height = height + 'px';
+ $('sc_drag_shadow_right').style.width = width + 'px';
+
+ height = (page.pageHeight - 1 - (parseInt(areaElement.style.top) +
+ parseInt(areaElement.style.height)));
+ height = (height < 0) ? 0 : height;
+ width = (page.pageWidth) - parseInt(areaElement.style.left);
+ width = (width < 0) ? 0 : width;
+ $('sc_drag_shadow_bottom').style.height = height + 'px';
+ $('sc_drag_shadow_bottom').style.width = width + 'px';
+ },
+
+ /**
+ * Remove selection area
+ */
+ removeSelectionArea: function() {
+ document.removeEventListener('mousedown', page.onMouseDown, false);
+ document.removeEventListener('mousemove', page.onMouseMove, false);
+ document.removeEventListener('mouseup', page.onMouseUp, false);
+ $('sc_drag_container').removeEventListener('dblclick',function() {
+ page.removeSelectionArea();
+ page.sendMessage({msg: 'capture_selected'});}, false);
+ page.removeElement('sc_drag_area_protector');
+ page.removeElement('sc_drag_area');
+ page.isSelectionAreaTurnOn = false;
+ },
+
+ /**
+ * Refresh the size info
+ */
+ updateSize: function() {
+ var width = Math.abs(page.endX - page.startX);
+ var height = Math.abs(page.endY - page.startY);
+ $('sc_drag_size').innerText = page.calculateSizeAfterZooming(width) +
+ ' x ' + page.calculateSizeAfterZooming(height);
+ },
+
+ /**
+ * create div
+ */
+ createDiv: function(parent, id) {
+ var divElement = document.createElement('div');
+ divElement.id = id;
+ parent.appendChild(divElement);
+ return divElement;
+ },
+
+ /**
+ * Remove an element
+ */
+ removeElement: function(id) {
+ if($(id)) {
+ $(id).parentNode.removeChild($(id));
+ }
+ },
+
+ injectCssResource: function(cssResource) {
+ var css = document.createElement('LINK');
+ css.type = 'text/css';
+ css.rel = 'stylesheet';
+ css.href = chrome.extension.getURL(cssResource);
+ (document.head || document.body || document.documentElement).
+ appendChild(css);
+ },
+
+ injectJavaScriptResource: function(scriptResource) {
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.charset = "utf-8";
+ script.src = chrome.extension.getURL(scriptResource);
+ (document.head || document.body || document.documentElement).
+ appendChild(script);
+ },
+
+ /**
+ * Remove an element
+ */
+ init: function() {
+ if (document.body.hasAttribute('screen_capture_injected')) {
+ return;
+ }
+ if (isPageCapturable()) {
+ chrome.extension.sendMessage({msg: 'page_capturable'});
+ } else {
+ chrome.extension.sendMessage({msg: 'page_uncapturable'});
+ }
+ this.injectCssResource('style.css');
+ this.addMessageListener();
+ this.injectJavaScriptResource("js/page_context.js");
+
+ // Retrieve original width of view port and cache.
+ page.getOriginalViewPortWidth();
+ }
+};
+
+/**
+ * Indicate if the current page can be captured.
+ */
+var isPageCapturable = function() {
+ return !page.checkPageIsOnlyEmbedElement();
+};
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+page.init();
+
+window.addEventListener('resize', function() {
+ if (page.isSelectionAreaTurnOn) {
+ page.removeSelectionArea();
+ page.showSelectionArea();
+ }
+
+ // Reget original width of view port if browser window resized or page zoomed.
+ page.getOriginalViewPortWidth();
+}, false);
+
+// Send page url for retriving and parsing access token for facebook and picasa.
+var message = {
+ msg: 'url_for_access_token',
+ url: window.location.href
+}
+if (window.location.href.indexOf('https://api.weibo.com/oauth2/default.html') == 0) {
+ message.siteId = 'sina'
+}
+page.sendMessage(message);
diff --git a/src/js/page_context.js b/src/js/page_context.js
new file mode 100644
index 0000000..ef35563
--- /dev/null
+++ b/src/js/page_context.js
@@ -0,0 +1,300 @@
+var __screenCapturePageContext__ = {
+ clone: function(object) {
+ function StubObj() { }
+ StubObj.prototype = object;
+ var newObj = new StubObj();
+ newObj.getInternalObject = function() {
+ return this.__proto__;
+ }
+ newObj.toString = function() {
+ try {
+ return this.__proto__.toString();
+ } catch (e) {
+ return 'object Object';
+ }
+ }
+ return newObj;
+ },
+
+ bind: function(newThis, func) {
+ var args = [];
+ for(var i = 2;i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ return function() {
+ return func.apply(newThis, args);
+ }
+ },
+
+ bodyWrapperDelegate_: null,
+ currentHookStatus_: false,
+
+ scrollValueHooker: function(oldValue, newValue, reason) {
+ // When we hook the value of scrollLeft/Top of body, it always returns 0.
+ return 0;
+ },
+
+ toggleBodyScrollValueHookStatus: function() {
+ this.currentHookStatus_ = !this.currentHookStatus_;
+ if (this.currentHookStatus_) {
+ var This = this;
+ try {
+ document.__defineGetter__('body', function() {
+ return This.bodyWrapperDelegate_.getWrapper();
+ });
+ } catch (e) {
+ window.console.log('error' + e);
+ }
+ this.bodyWrapperDelegate_.watch('scrollLeft', this.scrollValueHooker);
+ this.bodyWrapperDelegate_.watch('scrollTop', this.scrollValueHooker);
+ } else {
+ this.bodyWrapperDelegate_.unwatch('scrollLeft', this.scrollValueHooker);
+ this.bodyWrapperDelegate_.unwatch('scrollTop', this.scrollValueHooker);
+ var This = this;
+ try {
+ document.__defineGetter__('body', function() {
+ return This.bodyWrapperDelegate_.getWrapper().getInternalObject();
+ });
+ } catch (e) {
+ window.console.log('error' + e);
+ }
+ }
+ },
+
+ checkHookStatus: function() {
+ var needHookScrollValue = document.documentElement.getAttributeNode(
+ '__screen_capture_need_hook_scroll_value__');
+ needHookScrollValue =
+ !!(needHookScrollValue && needHookScrollValue.nodeValue == 'true');
+ if (this.currentHookStatus_ != needHookScrollValue)
+ this.toggleBodyScrollValueHookStatus();
+ },
+
+ init: function() {
+ if (!this.bodyWrapperDelegate_) {
+ this.bodyWrapperDelegate_ =
+ new __screenCapturePageContext__.ObjectWrapDelegate(
+ document.body, '^(DOCUMENT_[A-Z_]+|[A-Z_]+_NODE)$');
+ document.documentElement.addEventListener(
+ '__screen_capture_check_hook_status_event__',
+ __screenCapturePageContext__.bind(this, this.checkHookStatus));
+ }
+ }
+};
+
+// ObjectWrapDelegate class will create a empty object(wrapper), map its
+// prototype to the 'originalObject', then search all non-function properties
+// (except those properties which match the propertyNameFilter) of the
+// 'orginalObject' and set corresponding getter/setter to the wrapper.
+// Then you can manipulate the wrapper as 'originalObject' because the wrapper
+// use the corresponding getter/setter to access the corresponding properties in
+// 'originalObject' and the all function calls can be call through the prototype
+// inherit.
+// After createing the wrapper object, you can use watch method to monitor any
+// property which you want to know when it has been read(get) or change(set).
+// Please see the detail comment on method watch.
+// Remember the ObjectWrapDelegate returns the wrapDelegateObject instead of
+// really wrapper object. You have to use ObjectWrapDelegate.getWrapper to get
+// real wrapper object.
+// parameter @originalObject, object which you want to wrap
+// parameter @propertyNameFilter, string, regular expression pattern string for
+// those properties you don't put in the wrap object.
+__screenCapturePageContext__.ObjectWrapDelegate = function(
+ originalObject, propertyNameFilter) {
+ this.window_ = window;
+
+ // The wrapper is the object we use to wrap the 'originalObject'.
+ this.wrapper_ = __screenCapturePageContext__.clone(originalObject);
+
+ // This array saves all properties we set our getter/setter for them.
+ this.properties_ = [];
+
+ // This object to save all watch handlers. Each watch handler is bind to one
+ // certain property which is in properties_.
+ this.watcherTable_ = {};
+
+ // Check the propertyNameFilter parameter.
+ if (typeof propertyNameFilter == 'undefined') {
+ propertyNameFilter = '';
+ } else if (typeof propertyNameFilter != 'string') {
+ try {
+ propertyNameFilter = propertyNameFilter.toString();
+ } catch (e) {
+ propertyNameFilter = '';
+ }
+ }
+ if (propertyNameFilter.length) {
+ this.propertyNameFilter_ = new RegExp('');
+ this.propertyNameFilter_.compile(propertyNameFilter);
+ } else {
+ this.propertyNameFilter_ = null;
+ }
+
+ // For closure to access the private data of class.
+ var This = this;
+
+ // Set the getter object.
+ function setGetterAndSetter(wrapper, propertyName) {
+ wrapper.__defineGetter__(propertyName, function() {
+ var internalObj = this.getInternalObject();
+ var originalReturnValue = internalObj[propertyName];
+ var returnValue = originalReturnValue;
+
+ // See whether this property has been watched.
+ var watchers = This.watcherTable_[propertyName];
+ if (watchers) {
+ // copy the watcher to a cache in case someone call unwatch inside the
+ // watchHandler.
+ var watchersCache = watchers.concat();
+ for (var i = 0, l = watchersCache.length; i < l; ++i) {
+ var watcher = watchersCache[i];
+ if (!watcher) {
+ window.console.log('wrapper\'s watch for ' + propertyName +
+ ' is unavailable!');
+ continue; // should never happend
+ }
+ originalReturnValue = returnValue;
+ try {
+ returnValue = watcher(returnValue, returnValue, 'get');
+ } catch (e) {
+ returnValue = originalReturnValue;
+ }
+ }
+ }
+ return returnValue;
+ });
+
+ // Set the setter object.
+ wrapper.__defineSetter__(propertyName, function(value) {
+ var internalObj = this.getInternalObject();
+ var originalValue = value;
+ var userValue = originalValue;
+ var oldValue;
+ try {
+ oldValue = internalObj[propertyName];
+ } catch (e) {
+ oldValue = null;
+ }
+
+ // See whether this property has been watched.
+ var watchers = This.watcherTable_[propertyName];
+ if (watchers) {
+ // copy the watcher to a cache in case someone call unwatch inside the
+ // watchHandler.
+ var watchersCache = watchers.concat();
+ for (var i = 0, l = watchersCache.length; i < l; ++i) {
+ var watcher = watchersCache[i];
+ if (!watcher) {
+ window.console.log('wrapper\'s watch for ' + propertyName +
+ ' is unavailable!');
+ continue; // should never happend
+ }
+ originalValue = userValue;
+ try {
+ userValue = watcher(oldValue, userValue, 'set');
+ } catch (e) {
+ userValue = originalValue;
+ }
+ }
+ }
+ internalObj[propertyName] = userValue;
+ });
+ };
+
+ this.cleanUp_ = function() {
+ This.window_.removeEventListener('unload', This.cleanUp_, false);
+
+ // Delete all properties
+ for (var i = 0, l = This.properties_.length; i < l; ++i) {
+ delete This.wrapper_[This.properties_[i]];
+ }
+ This.window_ = null;
+ This.wrapper_ = null;
+ This.properties_ = null;
+ This.watcherTable_ = null;
+ This.propertyNameFilter_ = null;
+ This = null;
+ }
+
+ // We only bridge the non-function properties.
+ for (var prop in originalObject) {
+ if (this.propertyNameFilter_ && this.propertyNameFilter_.test(prop)) {
+ this.propertyNameFilter_.test('');
+ continue;
+ }
+ if (typeof originalObject[prop] != 'function') {
+ this.properties_.push(prop);
+ setGetterAndSetter(this.wrapper_, prop);
+ }
+ }
+
+ // Listen the unload event.
+ this.window_.addEventListener('unload', this.cleanUp_, false);
+};
+
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.getWrapper =
+ function() {
+ return this.wrapper_;
+}
+
+// Check whether a property is in the wrapper or not. If yes, return true.
+// Otherwise return false.
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.hasProperty =
+ function(propertyName) {
+ for (var i = 0, l = this.properties_.length; i < l; ++i) {
+ if (propertyName == this.properties_[i])
+ return true;
+ }
+ return false;
+}
+
+// Watches for a property to be accessed or be assigned a value and runs a
+// function when that occurs.
+// Watches for accessing a property or assignment to a property named prop in
+// this object, calling handler(oldval, newval, reason) whenever prop is
+// get/set and storing the return value in that property.
+// A watchpoint can filter (or nullify) the value assignment, by returning a
+// modified newval (or by returning oldval).
+// When watchpoint is trigering by get opeartor, the oldval is equal with
+// newval. The reason will be 'get'.
+// When watchpoint is trigering by set opeartor, The reason will be 'set'.
+// If you delete a property for which a watchpoint has been set,
+// that watchpoint does not disappear. If you later recreate the property,
+// the watchpoint is still in effect.
+// To remove a watchpoint, use the unwatch method.
+// If register the watchpoint successfully, return true. Otherwise return false.
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.watch = function(
+ propertyName, watchHandler) {
+ if (!this.hasProperty(propertyName))
+ return false;
+ var watchers = this.watcherTable_[propertyName];
+ if (watchers) {
+ for (var i = 0, l = watchers.length; i < l; ++i) {
+ if (watchHandler == watchers[i])
+ return true;
+ }
+ } else {
+ watchers = new Array();
+ this.watcherTable_[propertyName] = watchers;
+ }
+ watchers.push(watchHandler);
+ return true;
+}
+
+// Removes a watchpoint set with the watch method.
+__screenCapturePageContext__.ObjectWrapDelegate.prototype.unwatch = function(
+ propertyName, watchHandler) {
+ if (!this.hasProperty(propertyName))
+ return false;
+ var watchers = this.watcherTable_[propertyName];
+ if (watchers) {
+ for (var i = 0, l = watchers.length; i < l; ++i) {
+ if (watchHandler == watchers[i]) {
+ watchers.splice(i, 1);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+__screenCapturePageContext__.init();
diff --git a/src/js/picasa.js b/src/js/picasa.js
new file mode 100644
index 0000000..e6343a8
--- /dev/null
+++ b/src/js/picasa.js
@@ -0,0 +1,237 @@
+(function() {
+ const ALBUM_NAME = 'Screen Capture';
+ const CLIENT_ID = '368358534491.apps.googleusercontent.com';
+ const AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
+ const REDIRECT_URI = 'https://picasaweb.google.com';
+ const SCOPE = 'https://picasaweb.google.com/data/';
+ const RESPONSE_TYPE = 'token';
+ const ALBUM_URL = 'https://picasaweb.google.com/data/feed/api/user/' +
+ 'default';
+ const CREATE_ALBUM_URL = 'https://picasaweb.google.com/data/feed/api/user/' +
+ 'default';
+ const UPLOAD_BASE_URL = 'https://picasaweb.google.com/data/feed/api/user/' +
+ 'default/albumid/';
+ const LOGOUT_URL = 'https://picasaweb.google.com/bye?continue=' +
+ 'https://www.google.com/accounts/Logout?continue=' +
+ 'https://picasaweb.google.com';
+
+ // var picasa = window.Picasa = new Site('picasa');
+ var picasa = window.Picasa = {
+ siteId: 'picasa',
+ currentUserId: null,
+ redirectUrl: REDIRECT_URI,
+ accessTokenCallback: null,
+
+ getAccessToken: function(callback) {
+ picasa.accessTokenCallback = callback;
+ var url = AUTH_URL + '?client_id=' + CLIENT_ID + '&redirect_uri=' +
+ REDIRECT_URI + '&scope=' + SCOPE + '&response_type=' + RESPONSE_TYPE;
+ chrome.tabs.create({ url: url});
+ },
+
+ parseRedirectUrl: function(url) {
+ var result = false;
+ if (url.indexOf(REDIRECT_URI) == 0) {
+ var hash = url.split('#')[1];
+ if (hash) {
+ var params = hash.split('&');
+ var paramMap = {};
+ params.forEach(function(param) {
+ paramMap[param.split('=')[0]] = param.split('=')[1];
+ });
+
+ var accessToken = paramMap['access_token'];
+ var expires = paramMap['expires_in'];
+ if (accessToken && expires) {
+ result = {
+ accessToken: accessToken,
+ expires: expires
+ };
+ } else {
+ result = 'bad_redirect_url'; // Should never happened.
+ }
+ } else {
+ var search = url.split('?')[1];
+ if (search == 'error=access_denied')
+ result = 'access_denied';
+ }
+ }
+ return result;
+ },
+
+ isRedirectUrl: function(url) {
+ return picasa.parseRedirectUrl(url) != false;
+ },
+
+ parseAccessToken: function(redirectUrl) {
+ var parsedResult = picasa.parseRedirectUrl(redirectUrl);
+ if (parsedResult && typeof parsedResult == 'object') {
+ var user = new User({
+ accessToken: parsedResult.accessToken,
+ expires: new Date().getTime() + parsedResult.expires * 1000
+ });
+ picasa.accessTokenCallback('success', user);
+ } else {
+ picasa.accessTokenCallback('failure', 'user_denied');
+ }
+ picasa.accessTokenCallback = null;
+ },
+
+ getUserInfo: function(user, callback) {
+ ajax({
+ url: ALBUM_URL,
+ parameters: {
+ fields: 'title,gphoto:nickname,entry/title,entry/gphoto:id',
+ alt: 'json',
+ access_token: user.accessToken
+ },
+ success: function(res) {
+ var userId = res.feed.title.$t;
+ var userName = res.feed.gphoto$nickname.$t;
+ user.id = userId;
+ user.name = userName;
+
+ var albums = res.feed.entry;
+ if (albums) {
+ var length = albums.length;
+
+ // Check if user has created album "Screen Capture".
+ for (var i = 0; i < length; i++) {
+ var albumName = albums[i].title.$t;
+ if (albumName == ALBUM_NAME) {
+ user.albumId = albums[i].gphoto$id.$t;
+ break;
+ }
+ }
+ }
+
+ // Create album "Screen Capture" and retrieve album id.
+ if (!user.albumId) {
+ picasa.createAlbum(user.accessToken, function(result,
+ albumIdOrMessage) {
+ if (result == 'success') {
+ user.albumId = albumIdOrMessage;
+ callback('success', user);
+ } else {
+ callback('failure', albumIdOrMessage);
+ }
+ });
+ } else {
+ callback('success', user);
+ }
+ },
+ status: {
+ 404: function() {
+ callback('failure', 'failed_to_get_user_info');
+ }
+ }
+ });
+ },
+
+ createAlbum: function(accessToken, callback) {
+ var data = '<entry xmlns="http://www.w3.org/2005/Atom" ' +
+ 'xmlns:media="http://search.yahoo.com/mrss/" ' +
+ 'xmlns:gphoto="http://schemas.google.com/photos/2007">' +
+ '<title type="text">' + ALBUM_NAME +
+ '</title><category scheme="http://schemas.google.com/g/2005#kind" ' +
+ 'term="http://schemas.google.com/photos/2007#album"></category>' +
+ '</entry>';
+
+ ajax({
+ method: 'POST',
+ url: CREATE_ALBUM_URL,
+ parameters: {
+ alt: 'json'
+ },
+ data: data,
+ headers: {
+ 'GData-Version': 2,
+ 'Content-Type': 'application/atom+xml',
+ 'Authorization': 'OAuth ' + accessToken
+ },
+ complete: function(statusCode, album) {
+ if (statusCode == 201) {
+ var albumId = album.entry.gphoto$id.$t;
+ callback('success', albumId);
+ } else {
+ callback('failure', 'failure_to_create_album')
+ }
+ }
+ });
+ },
+
+ upload: function(user, caption, imageData, callback) {
+ caption = ajax.convertEntityString(caption);
+ caption = ajax.encodeForBinary(caption);
+
+ var imageFile = new Date().getTime() + '.png';
+ var headers = {
+ 'GData-Version': 2,
+ 'Content-Type': 'multipart/related; boundary=' +
+ MULTIPART_FORMDATA_BOUNDARY,
+ 'Authorization': 'OAuth ' + user.accessToken
+ };
+
+ var captionData = '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<title>' + imageFile + '</title>' +
+ '<summary>' + caption + '</summary>' +
+ '<category scheme="http://schemas.google.com/g/2005#kind" ' +
+ 'term="http://schemas.google.com/photos/2007#photo"/></entry>';
+
+ var dataPart1 = {
+ contentType: 'application/atom+xml',
+ data: captionData
+ };
+ var dataPart2 = {
+ contentType: 'image/png',
+ data: imageData
+ };
+ var multipartData = {
+ boundary: MULTIPART_FORMDATA_BOUNDARY,
+ dataList: [dataPart1, dataPart2]
+ };
+
+ ajax({
+ url: UPLOAD_BASE_URL + user.albumId + '?alt=json',
+ headers: headers,
+ multipartData: multipartData,
+ complete: function(statusCode, res) {
+ if (statusCode == 201) {
+ var link = res.entry.link;
+ callback('success', link);
+ } else {
+ var message = 'failed_to_upload_image';
+ if (statusCode == 403) {
+ // bad access token
+ message = 'bad_access_token';
+ } else if (statusCode == 404 && res == 'No album found.') {
+ // Invalid album id.
+ message = 'invalid_album_id'
+ }
+ callback('failure', message);
+ }
+ }
+ });
+ },
+
+ getPhotoLink: function(user, photolinks, callback) {
+ for (var i = 0; i < photolinks.length; i++) {
+ var link = photolinks[i];
+ if (link.type == 'text/html' &&
+ link.rel == 'http://schemas.google.com/photos/2007#canonical') {
+ callback('success', link.href);
+ break;
+ }
+ }
+ },
+
+ logout: function(callback) {
+ ajax({
+ url: LOGOUT_URL,
+ success: function() {
+ callback();
+ }
+ });
+ }
+ };
+})();
\ No newline at end of file
diff --git a/src/js/popup.js b/src/js/popup.js
new file mode 100644
index 0000000..3f5690f
--- /dev/null
+++ b/src/js/popup.js
@@ -0,0 +1,173 @@
+// Copyright (c) 2009 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 $(id) {
+ return document.getElementById(id);
+}
+
+chrome.extension.onMessage.addListener(function(request, sender, response) {
+ if (request.msg == 'page_capturable') {
+ $('tip').style.display = 'none';
+ $('captureSpecialPageItem').style.display = 'none';
+ $('captureWindowItem').style.display = 'block';
+ $('captureAreaItem').style.display = 'block';
+ $('captureWebpageItem').style.display = 'block';
+ } else if (request.msg == 'page_uncapturable') {
+ i18nReplace('tip', 'special');
+ $('tip').style.display = 'block';
+ $('captureSpecialPageItem').style.display = 'none';
+ $('captureWindowItem').style.display = 'none';
+ $('captureAreaItem').style.display = 'none';
+ $('captureWebpageItem').style.display = 'none';
+ }
+});
+
+function toDo(what) {
+ var bg = chrome.extension.getBackgroundPage();
+ switch (what) {
+ case 'capture_window':
+ bg.screenshot.captureWindow();
+ window.close();
+ break;
+ case 'capture_area':
+ bg.screenshot.showSelectionArea();
+ window.close();
+ break;
+ case 'capture_webpage':
+ bg.screenshot.captureWebpage();
+ $('loadDiv').style.display = 'block';
+ $('item').style.display = 'none';
+ break;
+ case 'capture_special_page':
+ bg.screenshot.captureSpecialPage();
+ window.close();
+ break;
+ }
+}
+
+function i18nReplace(id, name) {
+ return $(id).innerHTML = chrome.i18n.getMessage(name);
+}
+
+function resizeDivWidth(id, width) {
+ $(id).style.width = width + "px";
+}
+
+function init() {
+ i18nReplace('captureSpecialPageText', 'capture_window');
+ i18nReplace('capturing', 'capturing');
+ i18nReplace('captureWindowText', 'capture_window');
+ i18nReplace('captureAreaText', 'capture_area');
+ i18nReplace('captureWebpageText', 'capture_webpage');
+ i18nReplace('optionItem', 'option');
+
+ $('option').addEventListener('click', function () {
+ chrome.tabs.create({ url: 'options.html'});
+ }, false);
+ if (HotKey.isEnabled()) {
+ $('captureWindowShortcut').style.display = 'inline';
+ $('captureAreaShortcut').style.display = 'inline';
+ $('captureWebpageShortcut').style.display = 'inline';
+ document.body.style.minWidth = "190px"
+ } else {
+ $('captureWindowShortcut').style.display = 'none';
+ $('captureAreaShortcut').style.display = 'none';
+ $('captureWebpageShortcut').style.display = 'none';
+ document.body.style.minWidth = "140px";
+ }
+ var isScriptLoad = false;
+ chrome.tabs.getSelected(null, function(tab) {
+ if (tab.url.indexOf('chrome') == 0 || tab.url.indexOf('about') == 0) {
+ i18nReplace('tip', 'special');
+ return;
+ } else {
+ $('tip').style.display = 'none';
+ $('captureSpecialPageItem').style.display = 'block';
+ showOption();
+ }
+ chrome.tabs.sendMessage(tab.id, {msg: 'is_page_capturable'},
+ function(response) {
+ isScriptLoad = true;
+ if (response.msg == 'capturable') {
+ $('tip').style.display = 'none';
+ $('captureSpecialPageItem').style.display = 'none';
+ $('captureWindowItem').style.display = 'block';
+ $('captureAreaItem').style.display = 'block';
+ $('captureWebpageItem').style.display = 'block';
+ var textWidth = $('captureWindowText')['scrollWidth'];
+ resizeDivWidth('captureWindowText', textWidth);
+ resizeDivWidth('captureAreaText', textWidth);
+ resizeDivWidth('captureWebpageText', textWidth);
+ var bg = chrome.extension.getBackgroundPage();
+ if (bg.screenshot.isThisPlatform('mac')) {
+ $('captureAreaShortcut').innerText = '\u2325\u2318R';
+ $('captureWindowShortcut').innerText = '\u2325\u2318V';
+ $('captureWebpageShortcut').innerText = '\u2325\u2318H';
+ }
+ } else if (response.msg == 'uncapturable') {
+ i18nReplace('tip', 'special');
+ $('tip').style.display = 'block';
+ } else {
+ i18nReplace('tip', 'loading');
+ }
+ });
+ });
+ chrome.tabs.executeScript(null, {file: 'js/isLoad.js'});
+ var insertScript = function() {
+ if (isScriptLoad == false) {
+ chrome.tabs.getSelected(null, function(tab) {
+ if (tab.url.indexOf('chrome') == 0 ||
+ tab.url.indexOf('about') == 0) {
+ i18nReplace('tip', 'special');
+ } else {
+ $('tip').style.display = 'none';
+ $('captureSpecialPageItem').style.display = 'block';
+ showOption();
+ }
+ });
+ }
+ var captureItems = document.querySelectorAll('li.menuI');
+ var showSeparator = false;
+ for (var i = 0; i < captureItems.length; i++) {
+ if (window.getComputedStyle(captureItems[i]).display != 'none') {
+ showSeparator = true;
+ break;
+ }
+ }
+ $('separatorItem').style.display = showSeparator ? 'block' : 'none';
+ }
+ setTimeout(insertScript, 500);
+
+ // Update hot key.
+ if (HotKey.get('area') != '@')
+ $('captureAreaShortcut').innerText = 'Ctrl+Alt+' + HotKey.get('area');
+ if (HotKey.get('viewport') != '@') {
+ $('captureWindowShortcut').innerText = 'Ctrl+Alt+' +
+ HotKey.get('viewport');
+ }
+ if (HotKey.get('fullpage') != '@') {
+ $('captureWebpageShortcut').innerText ='Ctrl+Alt+' +
+ HotKey.get('fullpage');
+ }
+
+ $('captureSpecialPageItem').addEventListener('click', function(e) {
+ toDo('capture_special_page');
+ });
+ $('captureAreaItem').addEventListener('click', function(e) {
+ toDo('capture_area');
+ });
+ $('captureWindowItem').addEventListener('click', function(e) {
+ toDo('capture_window');
+ });
+ $('captureWebpageItem').addEventListener('click', function(e) {
+ toDo('capture_webpage');
+ });
+}
+
+function showOption() {
+ $('option').style.display = 'block';
+ $('separatorItem').style.display = 'block';
+}
+
+document.addEventListener('DOMContentLoaded', init);
diff --git a/src/js/sha1.js b/src/js/sha1.js
new file mode 100644
index 0000000..1b55982
--- /dev/null
+++ b/src/js/sha1.js
@@ -0,0 +1,202 @@
+/*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS PUB 180-1
+ * Version 2.1a Copyright Paul Johnston 2000 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
+var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
+function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
+function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
+function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
+function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
+function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function sha1_vm_test()
+{
+ return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
+}
+
+/*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+function core_sha1(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w = Array(80);
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+ var e = -1009589776;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+ var olde = e;
+
+ for(var j = 0; j < 80; j++)
+ {
+ if(j < 16) w[j] = x[i + j];
+ else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+ var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+ return Array(a, b, c, d, e);
+
+}
+
+/*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+function sha1_ft(t, b, c, d)
+{
+ if(t < 20) return (b & c) | ((~b) & d);
+ if(t < 40) return b ^ c ^ d;
+ if(t < 60) return (b & c) | (b & d) | (c & d);
+ return b ^ c ^ d;
+}
+
+/*
+ * Determine the appropriate additive constant for the current iteration
+ */
+function sha1_kt(t)
+{
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
+ (t < 60) ? -1894007588 : -899497514;
+}
+
+/*
+ * Calculate the HMAC-SHA1 of a key and some data
+ */
+function core_hmac_sha1(key, data)
+{
+ var bkey = str2binb(key);
+ if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
+ return core_sha1(opad.concat(hash), 512 + 160);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert an 8-bit or 16-bit string to an array of big-endian words
+ * In 8-bit function, characters >255 have their hi-byte silently ignored.
+ */
+function str2binb(str)
+{
+ var bin = Array();
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < str.length * chrsz; i += chrsz)
+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
+ return bin;
+}
+
+/*
+ * Convert an array of big-endian words to a string
+ */
+function binb2str(bin)
+{
+ var str = "";
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < bin.length * 32; i += chrsz)
+ str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a hex string.
+ */
+function binb2hex(binarray)
+{
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i++)
+ {
+ str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
+ hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
+ }
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a base-64 string
+ */
+function binb2b64(binarray)
+{
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i += 3)
+ {
+ var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
+ | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
+ | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+ else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+ }
+ }
+ return str;
+}
diff --git a/src/js/shortcut.js b/src/js/shortcut.js
new file mode 100644
index 0000000..7959178
--- /dev/null
+++ b/src/js/shortcut.js
@@ -0,0 +1,35 @@
+var shortcutKey = {
+
+ init: function() {
+ if (document.body.hasAttribute('screen_capture_injected')) {
+ return;
+ }
+ document.body.setAttribute('screen_capture_injected', true);
+ document.body.addEventListener('keydown', shortcutKey.handleShortcut,
+ false);
+ },
+
+ isThisPlatform: function(operationSystem) {
+ return navigator.userAgent.toLowerCase().indexOf(operationSystem) > -1;
+ },
+
+ handleShortcut: function (event) {
+ var isMac = shortcutKey.isThisPlatform('mac');
+ var keyCode = event.keyCode;
+ // Send compose key like Ctrl + Alt + alphabetical-key to background.
+ if ((event.ctrlKey && event.altKey && !isMac ||
+ event.metaKey && event.altKey && isMac) &&
+ keyCode > 64 && keyCode < 91) {
+ shortcutKey.sendMessage({
+ msg: 'capture_hot_key',
+ keyCode: keyCode
+ });
+ }
+ },
+
+ sendMessage: function(message) {
+ chrome.extension.sendMessage(message);
+ }
+};
+
+shortcutKey.init();
diff --git a/src/js/showimage.js b/src/js/showimage.js
new file mode 100644
index 0000000..294353d
--- /dev/null
+++ b/src/js/showimage.js
@@ -0,0 +1,824 @@
+// Copyright (c) 2009 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 isHighVersion() {
+ var version = navigator.userAgent.match(/Chrome\/(\d+)/)[1];
+ return version > 9;
+}
+
+function $(id) {
+ return document.getElementById(id);
+}
+function i18nReplace(id, messageKey) {
+ return $(id).innerHTML = chrome.i18n.getMessage(messageKey);
+}
+UploadUI.init();
+
+var bg = chrome.extension.getBackgroundPage();
+var canvas = new Canvas();
+var photoshop = {
+ canvas: document.createElement("canvas"),
+ tabTitle: '',
+ startX: 0,
+ startY: 0,
+ endX: 0,
+ endY: 0,
+ dragFlag: false,
+ flag: 'rectangle',
+ layerId: 'layer0',
+ canvasId: '',
+ color: '#ff0000',
+ highlightColor: '',
+ lastValidAction: 0,
+ markedArea: [],
+ isDraw: true,
+ offsetX: 0,
+ offsetY: 36,
+ nowHeight: 0,
+ nowWidth: 0,
+ highlightType: 'border',
+ highlightMode: 'rectangle',
+ text: '',
+
+ i18nReplace: i18nReplace,
+
+ initCanvas: function() {
+ $('canvas').width = $('mask-canvas').width = $('photo').style.width =
+ photoshop.canvas.width = bg.screenshot.canvas.width;
+ $('canvas').height = $('mask-canvas').height = $('photo').style.height =
+ photoshop.canvas.height = bg.screenshot.canvas.height;
+ var context = photoshop.canvas.getContext('2d');
+ context.drawImage(bg.screenshot.canvas, 0, 0);
+ context = $('canvas').getContext('2d');
+ context.drawImage(photoshop.canvas, 0, 0);
+ $('canvas').style.display = 'block';
+ },
+
+ init: function() {
+ photoshop.initTools();
+ photoshop.initCanvas();
+ photoshop.tabTitle = bg.screenshot.tab.title;
+ var showBoxHeight = function() {
+ $('showBox').style.height = window.innerHeight - photoshop.offsetY - 1;
+ }
+ setTimeout(showBoxHeight, 50);
+ },
+
+ markCurrentElement: function(element) {
+ if (element && element.parentNode) {
+ var children = element.parentNode.children;
+ for (var i = 0; i < children.length; i++) {
+ var node = children[i];
+ if (node == element) {
+ element.className = 'mark';
+ } else {
+ node.className = '';
+ }
+ }
+ }
+ },
+
+ setHighLightMode: function() {
+ photoshop.highlightType = localStorage.highlightType || 'border';
+ photoshop.color = localStorage.highlightColor || '#FF0000';
+ $(photoshop.layerId).style.border = '2px solid ' + photoshop.color;
+ if (photoshop.highlightType == 'rect') {
+ $(photoshop.layerId).style.backgroundColor = photoshop.color;
+ $(photoshop.layerId).style.opacity = 0.5;
+ }
+ if (photoshop.flag == 'rectangle') {
+ $(photoshop.layerId).style.borderRadius = '0 0';
+ } else if (photoshop.flag == 'radiusRectangle') {
+ $(photoshop.layerId).style.borderRadius = '6px 6px';
+ } else if (photoshop.flag == 'ellipse') {
+ $(photoshop.layerId).style.border = '0';
+ $(photoshop.layerId).style.backgroundColor = '';
+ $(photoshop.layerId).style.opacity = 1;
+ }
+
+ },
+
+ setBlackoutMode: function() {
+ photoshop.color = '#000000';
+ $(photoshop.layerId).style.opacity = 1;
+ $(photoshop.layerId).style.backgroundColor = '#000000';
+ $(photoshop.layerId).style.border = '2px solid #000000';
+ },
+
+ setTextMode: function() {
+ localStorage.fontSize = localStorage.fontSize || '16';
+ photoshop.color = localStorage.fontColor =
+ localStorage.fontColor || '#FF0000';
+ $(photoshop.layerId).setAttribute('contentEditable', true);
+ $(photoshop.layerId).style.border = '1px dotted #333333';
+ $(photoshop.layerId).style.cursor = 'text';
+ $(photoshop.layerId).style.lineHeight = localStorage.fontSize + 'px';
+ $(photoshop.layerId).style.fontSize = localStorage.fontSize + 'px';
+ $(photoshop.layerId).style.color = photoshop.color;
+ $(photoshop.layerId).innerHTML = '<br/>';
+ var layer = $(photoshop.layerId);
+ var id = photoshop.layerId;
+ layer.addEventListener('blur', function() {
+ photoshop.setTextToArray(id);
+ }, true);
+ layer.addEventListener('click', function() {
+ this.style.border = '1px dotted #333333';
+ }, true);
+ layer.addEventListener('mouseout', function() {
+ if (!photoshop.dragFlag) {
+ this.style.borderWidth = 0;
+ }
+ }, false);
+ layer.addEventListener('mousemove', function() {
+ this.style.border = '1px dotted #333333';
+ }, false);
+ },
+
+ setTextToArray: function(id) {
+ var str = $(id).innerText.split("\n");
+ if (photoshop.markedArea.length > 0) {
+ for (var i = photoshop.markedArea.length - 1; i >= 0; i--) {
+ if (photoshop.markedArea[i].id == id) {
+ photoshop.markedArea[i].context = str;
+ break;
+ }
+ }
+ $(id).style.borderWidth = 0;
+ }
+ },
+
+ openOptionPage: function() {
+ chrome.tabs.create({url: chrome.extension.getURL("options.html")});
+ },
+
+ closeCurrentTab: function() {
+ chrome.tabs.getSelected(null, function(tab) {
+ chrome.tabs.remove(tab.id);
+ });
+ },
+
+ finish: function() {
+ var context = $('canvas').getContext('2d');
+ context.drawImage(photoshop.canvas, 0, 0);
+ },
+
+ colorRgba: function(color, opacity) {
+ var sColor = color.toLowerCase();
+ var sColorChange = [];
+ for (var i = 1; i < sColor.length; i += 2) {
+ sColorChange.push(parseInt("0x" + sColor.slice(i,i + 2)));
+ }
+ return "rgba(" + sColorChange.join(",") + "," + opacity + ")";
+ },
+
+ /**
+ * Undo the current operation
+ */
+ toDo: function(element, what) {
+ photoshop.flag = what;
+ photoshop.isDraw = true;
+ photoshop.markCurrentElement(element);
+ },
+
+ setDivStyle: function(x, y) {
+ $(photoshop.layerId).setAttribute("style", "");
+ $(photoshop.layerId).setAttribute("contentEditable", false);
+ switch(photoshop.flag) {
+ case 'rectangle':
+ case 'radiusRectangle':
+ case 'ellipse':
+ photoshop.setHighLightMode();
+ break;
+ case 'redact':
+ photoshop.setBlackoutMode();
+ break;
+ case 'text':
+ photoshop.setTextMode();
+ break;
+ case 'line':
+ case 'arrow':
+ photoshop.drawLineOnMaskCanvas(x, y, x, y, 'lineStart',
+ photoshop.layerId);
+ break;
+ case 'blur':
+ photoshop.createCanvas(photoshop.layerId);
+ break;
+ }
+ },
+
+ /**
+ * Create a layer and set style
+ */
+ createDiv: function() {
+ photoshop.lastValidAction++;
+ photoshop.layerId = 'layer' + photoshop.lastValidAction;
+ if ($(photoshop.layerId)) {
+ photoshop.removeElement(photoshop.layerId);
+ }
+ var divElement = document.createElement('div');
+ divElement.id = photoshop.layerId;
+ divElement.className = 'layer';
+ $('photo').appendChild(divElement);
+ if (photoshop.flag == 'blur') {
+ photoshop.createCanvas(photoshop.layerId);
+ }
+ return divElement;
+ },
+
+ createCanvas: function(parentId) {
+ photoshop.canvasId = 'cav-' + parentId;
+ if (!$(photoshop.canvasId)) {
+ var cav = document.createElement('canvas');
+ cav.id = photoshop.canvasId;
+ cav.width = 10;
+ cav.height = 10;
+ $(photoshop.layerId).appendChild(cav);
+ return cav;
+ }
+ return $(photoshop.canvasId);
+
+ },
+
+ createCloseButton: function(parent, id, left, top, flag) {
+ var imgElement = document.createElement('img');
+ imgElement.id = id;
+ imgElement.src = 'images/cross.png';
+ imgElement.className = 'closeButton';
+ imgElement.style.left = left - 15 + 'px';
+ if (photoshop.flag == 'line' || photoshop.flag == 'arrow') {
+ imgElement.style.left = left / 2 - 5 + 'px';
+ imgElement.style.top = top / 2 - 5 + 'px';
+ }
+ imgElement.onclick = function() {
+ $(parent).style.display = 'none';
+ photoshop.removeLayer(parent);
+ };
+ $(parent).onmousemove = function() {
+ if (!photoshop.dragFlag) {
+ photoshop.showCloseButton(id);
+ $(parent).style.zIndex = 110;
+ photoshop.isDraw = (flag == 'text' ? false : photoshop.isDraw);
+ }
+ };
+ $(parent).onmouseout = function() {
+ photoshop.hideCloseButton(id);
+ $(parent).style.zIndex = 100;
+ photoshop.isDraw = true;
+ };
+ $(parent).appendChild(imgElement);
+ return imgElement;
+ },
+
+ showCloseButton: function(id) {
+ $(id).style.display = 'block';
+ },
+
+ hideCloseButton: function(id) {
+ $(id).style.display = 'none';
+ photoshop.isDraw = true;
+ },
+
+ removeLayer: function(id) {
+ for (var i = 0; i < photoshop.markedArea.length; i++) {
+ if (photoshop.markedArea[i].id == id) {
+ photoshop.markedArea.splice(i, 1);
+ break;
+ }
+ }
+ photoshop.removeElement(id);
+ },
+
+ /**
+ * Set the starting point(x,y) when mouse pressed
+ */
+ onMouseDown: function(event) {
+ if (photoshop.isDraw && event.button != 2) {
+ photoshop.startX = event.pageX + $('showBox').scrollLeft -
+ photoshop.offsetX;
+ photoshop.startY = event.pageY + $('showBox').scrollTop -
+ photoshop.offsetY;
+ photoshop.setDivStyle(photoshop.startX, photoshop.startY);
+ photoshop.dragFlag = true;
+
+ $(photoshop.layerId).style.left = photoshop.startX + 'px';
+ $(photoshop.layerId).style.top = photoshop.startY + 'px';
+ $(photoshop.layerId).style.height = 0;
+ $(photoshop.layerId).style.width = 0;
+ $(photoshop.layerId).style.display = 'block';
+ }
+ },
+
+ onMouseUp: function(event) {
+ $('mask-canvas').style.zIndex = 10;
+ photoshop.endX = event.pageX + $('showBox').scrollLeft -
+ photoshop.offsetX;
+ if (photoshop.endX > photoshop.canvas.width) {
+ photoshop.endX = photoshop.canvas.width ;
+ }
+
+ if (photoshop.endX < 0) {
+ photoshop.endX = 0;
+ }
+
+ photoshop.endY = event.pageY + $('showBox').scrollTop -
+ photoshop.offsetY;
+ if (photoshop.endY > photoshop.canvas.height) {
+ photoshop.endY = photoshop.canvas.height ;
+ }
+ if (photoshop.endY < 0) {
+ photoshop.endY = 0;
+ }
+ if (photoshop.isDraw && photoshop.dragFlag && (photoshop.endX !=
+ photoshop.startX || photoshop.endY != photoshop.startY)) {
+ if (photoshop.flag == 'line' || photoshop.flag == 'arrow') {
+ photoshop.drawLineOnMaskCanvas(photoshop.startX, photoshop.startY,
+ photoshop.endX, photoshop.endY, 'drawEnd', photoshop.layerId);
+ } else if (photoshop.flag == 'blur') {
+ canvas.blurImage(photoshop.canvas, $(photoshop.canvasId),
+ photoshop.layerId, photoshop.startX, photoshop.startY,
+ photoshop.endX, photoshop.endY);
+ } else if (photoshop.flag == 'ellipse') {
+ photoshop.drawEllipseOnMaskCanvas(photoshop.endX,
+ photoshop.endY, 'end', photoshop.layerId);
+ }
+ photoshop.markedArea.push({
+ 'id': photoshop.layerId,
+ 'startX': photoshop.startX,
+ 'startY': photoshop.startY,
+ 'endX': photoshop.endX,
+ 'endY': photoshop.endY,
+ 'width': photoshop.nowWidth,
+ 'height': photoshop.nowHeight,
+ 'flag': photoshop.flag,
+ 'highlightType': photoshop.highlightType,
+ 'fontSize': localStorage.fontSize,
+ 'color': photoshop.color,
+ 'context': ''
+ });
+ $(photoshop.layerId).focus();
+ var imageBtnId = 'close_' + photoshop.layerId;
+ photoshop.createCloseButton(photoshop.layerId, imageBtnId,
+ photoshop.nowWidth, photoshop.nowHeight, photoshop.flag);
+ photoshop.createDiv();
+ } else if (photoshop.endX ==
+ photoshop.startX && photoshop.endY == photoshop.startY) {
+ photoshop.removeElement(photoshop.layerId);
+ photoshop.createDiv();
+ }
+ photoshop.dragFlag = false;
+
+ },
+
+ /**
+ * Refresh div‘s height and width when the mouse move
+ */
+ onMouseMove: function(event) {
+ if(photoshop.dragFlag) {
+ $('mask-canvas').style.zIndex = 200;
+ photoshop.endX = event.pageX + $('showBox').scrollLeft -
+ photoshop.offsetX;
+ if (photoshop.endX > photoshop.canvas.width) {
+ photoshop.endX = photoshop.canvas.width ;
+ }
+
+ if (photoshop.endX < 0) {
+ photoshop.endX = 0;
+ }
+
+ photoshop.endY = event.pageY + $('showBox').scrollTop -
+ photoshop.offsetY;
+ if (photoshop.endY > photoshop.canvas.height) {
+ photoshop.endY = photoshop.canvas.height ;
+ }
+ if (photoshop.endY < 0) {
+ photoshop.endY = 0;
+ }
+ photoshop.nowHeight = photoshop.endY - photoshop.startY - 1 ;
+ photoshop.nowWidth = photoshop.endX - photoshop.startX - 1 ;
+
+ if(photoshop.nowHeight < 0) {
+ $(photoshop.layerId).style.top = photoshop.endY + 'px';
+ photoshop.nowHeight = -1 * photoshop.nowHeight;
+ }
+ if(photoshop.nowWidth < 0) {
+ $(photoshop.layerId).style.left = photoshop.endX + 'px';
+ photoshop.nowWidth = -1 * photoshop.nowWidth;
+ }
+
+ $(photoshop.layerId).style.height = photoshop.nowHeight - 3;
+ $(photoshop.layerId).style.width = photoshop.nowWidth - 3;
+ if (photoshop.flag == 'line' || photoshop.flag == 'arrow') {
+ photoshop.drawLineOnMaskCanvas(photoshop.startX, photoshop.startY,
+ photoshop.endX, photoshop.endY, 'lineDrawing', photoshop.layerId);
+ } else if (photoshop.flag == 'blur') {
+ $(photoshop.layerId).style.height = photoshop.nowHeight ;
+ $(photoshop.layerId).style.width = photoshop.nowWidth ;
+ canvas.blurImage(photoshop.canvas, $(photoshop.canvasId),
+ photoshop.layerId, photoshop.startX, photoshop.startY,
+ photoshop.endX, photoshop.endY);
+ } else if (photoshop.flag == 'ellipse') {
+ photoshop.drawEllipseOnMaskCanvas(photoshop.endX,
+ photoshop.endY, 'drawing', photoshop.layerId);
+ }
+ }
+
+ },
+
+ /**
+ * Remove a div
+ */
+ removeElement: function(id) {
+ if($(id)) {
+ $(id).parentNode.removeChild($(id));
+ }
+ },
+
+ /**
+ * Use fillStyle, fillText and fillRect functions to draw rectangles,
+ * and render to canvas
+ */
+ draw: function() {
+ var context = $('canvas').getContext('2d');
+ for (var j = 0; j < photoshop.markedArea.length; j++) {
+ var mark = photoshop.markedArea[j];
+ var x = (mark.startX < mark.endX) ? mark.startX : mark.endX;
+ var y = (mark.startY < mark.endY) ? mark.startY : mark.endY;
+ var width = mark.width;
+ var height = mark.height;
+ var color = mark.color;
+ switch(mark.flag) {
+ case 'rectangle':
+ if (mark.highlightType == 'border') {
+ canvas.drawStrokeRect(context, color, x, y, width, height, 2);
+ } else {
+ var color = changeColorToRgba(color, 0.5);
+ canvas.drawFillRect(context, color, x, y, width, height);
+ }
+ break;
+ case 'radiusRectangle':
+ canvas.drawRoundedRect(
+ context, color, x, y, width, height, 6, mark.highlightType);
+ break;
+ case 'ellipse':
+ x = (mark.startX + mark.endX) / 2;
+ y = (mark.startY + mark.endY) / 2;
+ var xAxis = Math.abs(mark.endX - mark.startX) / 2;
+ var yAxis = Math.abs(mark.endY - mark.startY) / 2;
+ canvas.drawEllipse(
+ context, color, x, y, xAxis, yAxis, 3, mark.highlightType);
+ break;
+ case 'redact':
+ canvas.drawFillRect(context, color, x, y, width, height);
+ break;
+ case 'text':
+ for (var i = 0; i < mark.context.length; i++) {
+ canvas.setText(
+ context, mark.context[i], color, mark.fontSize, 'arial',
+ mark.fontSize, x, y + mark.fontSize * i, width);
+ }
+ break;
+ case 'blur':
+ var imageData = context.getImageData(
+ x, y, photoshop.markedArea[j].width,
+ photoshop.markedArea[j].height);
+ imageData = canvas.boxBlur(
+ imageData, photoshop.markedArea[j].width,
+ photoshop.markedArea[j].height, 10);
+ context.putImageData(
+ imageData, x, y, 0, 0, photoshop.markedArea[j].width,
+ photoshop.markedArea[j].height);
+ break;
+ case 'line':
+ canvas.drawLine(
+ context, color, 'round', 2,
+ mark.startX, mark.startY, mark.endX, mark.endY);
+ break;
+ case 'arrow':
+ canvas.drawArrow(
+ context, color, 2, 4, 10, 'round',
+ mark.startX, mark.startY, mark.endX, mark.endY);
+ break;
+ }
+ }
+ },
+
+ drawLineOnMaskCanvas: function(startX, startY, endX, endY, type, layerId) {
+ var ctx = $('mask-canvas').getContext('2d');
+ ctx.clearRect(0, 0, $('mask-canvas').width, $('mask-canvas').height);
+ if (type == 'drawEnd') {
+ var offset = 20;
+ var width = Math.abs(endX - photoshop.startX) > 0 ?
+ Math.abs(endX - photoshop.startX): 0;
+ var height = Math.abs(endY - photoshop.startY) > 0 ?
+ Math.abs(endY - photoshop.startY): 0;
+ var offsetLeft = parseInt($(layerId).style.left);
+ var offsetTop = parseInt($(layerId).style.top);
+ startX = startX - offsetLeft + offset / 2;
+ startY = startY - offsetTop + offset / 2;
+ endX = endX - offsetLeft + offset / 2;
+ endY = endY - offsetTop + offset / 2;
+ $(layerId).style.left = offsetLeft - offset / 2;
+ $(layerId).style.top = offsetTop - offset / 2;
+ var cavCopy = photoshop.createCanvas(layerId);
+ cavCopy.width = width + offset;
+ cavCopy.height = height + offset;
+ ctx = cavCopy.getContext('2d');
+ }
+ if (localStorage.lineType == 'line') {
+ canvas.drawLine(ctx, localStorage.lineColor, 'round', 2,
+ startX, startY, endX, endY);
+ } else {
+ canvas.drawArrow(ctx, localStorage.lineColor, 2, 4, 10, 'round',
+ startX, startY, endX, endY)
+ }
+
+ },
+
+ createColorPadStr: function(element, type) {
+ var colorList = ['#000000', '#0036ff', '#008000', '#dacb23', '#d56400',
+ '#c70000', '#be00b3', '#1e2188', '#0090ff', '#22cc01', '#ffff00',
+ '#ff9600', '#ff0000', '#ff008e', '#7072c3', '#49d2ff', '#9dff3d',
+ '#ffffff', '#ffbb59', '#ff6b6b', '#ff6bbd'];
+
+ var div = document.createElement("div");
+ div.id = "colorpad";
+ element.appendChild(div);
+
+ for(var i = 0; i < colorList.length; i++) {
+ var a = document.createElement("a");
+ var color = colorList[i];
+ a.id = color;
+ a.title = color;
+ a.style.backgroundColor = color;
+ if (color == '#ffffff') {
+ a.style.border = "1px solid #444";
+ a.style.width = "12px"
+ a.style.height = "12px";
+ }
+ a.addEventListener('click', function(e) {
+ photoshop.colorPadPick(e.target.id, type);
+ return false;
+ });
+ div.appendChild(a);
+ }
+ },
+
+ colorPadPick: function(color, type) {
+ photoshop.color = color;
+ if(type == 'highlight') {
+ localStorage.highlightColor = color;
+ photoshop.setHighlightColorBoxStyle(color);
+ } else if(type == 'text') {
+ localStorage.fontColor = color;
+ $('fontColorBox').style.backgroundColor = color;
+ } else if (type == 'line') {
+ localStorage.lineColor = color;
+ photoshop.setLineColorBoxStyle();
+ } else if (type == 'ellipse') {
+ $('ellipseBox').style.borderColor = color;
+ }
+ },
+
+ setHighlightColorBoxStyle: function(color) {
+ var highlightColorBox = $('highlightColorBox');
+ highlightColorBox.style.borderColor = color;
+ localStorage.highlightType = localStorage.highlightType || 'border';
+ if (localStorage.highlightType == 'border') {
+ highlightColorBox.style.background = '#ffffff';
+ highlightColorBox.style.opacity = 1;
+ $('borderMode').className = 'mark';
+ $('rectMode').className = '';
+ } else if (localStorage.highlightType == 'rect') {
+ highlightColorBox.style.background = color;
+ highlightColorBox.style.opacity = 0.5;
+ $('borderMode').className = '';
+ $('rectMode').className = 'mark';
+ }
+ if (photoshop.flag == 'rectangle') {
+ highlightColorBox.style.borderRadius = '0 0';
+ } else if (photoshop.flag == 'radiusRectangle') {
+ highlightColorBox.style.borderRadius = '3px 3px';
+ } else if (photoshop.flag == 'ellipse') {
+ highlightColorBox.style.borderRadius = '12px 12px';
+ }
+ photoshop.markCurrentElement($(photoshop.flag));
+ },
+
+ setBlackoutColorBoxStyle: function() {
+ localStorage.blackoutType = localStorage.blackoutType || 'redact';
+ if (localStorage.blackoutType == 'redact') {
+ $('blackoutBox').className = 'rectBox';
+ $('redact').className = 'mark';
+ $('blur').className = '';
+ } else if (localStorage.blackoutType == 'blur') {
+ $('blackoutBox').className = 'blurBox';
+ $('redact').className = '';
+ $('blur').className = 'mark';
+ }
+ },
+
+ setFontSize: function(size) {
+ var id = 'size_' + size;
+ localStorage.fontSize = size;
+ $('size_10').className = '';
+ $('size_16').className = '';
+ $('size_18').className = '';
+ $('size_32').className = '';
+ $(id).className = 'mark';
+ },
+
+ setLineColorBoxStyle: function() {
+ localStorage.lineType = localStorage.lineType || 'line';
+ photoshop.color = localStorage.lineColor =
+ localStorage.lineColor || '#FF0000';
+ var ctx = $('lineIconCav').getContext('2d');
+ ctx.clearRect(0, 0, 14, 14);
+ if (localStorage.lineType == 'line') {
+ $('straightLine').className = 'mark';
+ $('arrow').className = '';
+ canvas.drawLine(ctx, photoshop.color, 'round', 2, 1, 13, 13, 1);
+ } else if (localStorage.lineType == 'arrow') {
+ $('straightLine').className = '';
+ $('arrow').className = 'mark';
+ canvas.drawArrow(ctx, photoshop.color, 2, 4, 7, 'round',1, 13, 13, 1);
+ }
+
+ },
+
+ initTools: function() {
+ photoshop.i18nReplace('tHighlight', 'highlight');
+ photoshop.i18nReplace('tRedact', 'redact');
+ photoshop.i18nReplace('redactText', 'solid_black');
+ photoshop.i18nReplace('tText', 'text');
+ photoshop.i18nReplace('tSave', 'save');
+ photoshop.i18nReplace('tClose', 'close');
+ photoshop.i18nReplace('border', 'border');
+ photoshop.i18nReplace('rect', 'rect');
+ photoshop.i18nReplace('blurText', 'blur');
+ photoshop.i18nReplace('lineText', 'line');
+ photoshop.i18nReplace('size_10', 'size_small');
+ photoshop.i18nReplace('size_16', 'size_normal');
+ photoshop.i18nReplace('size_18', 'size_large');
+ photoshop.i18nReplace('size_32', 'size_huge');
+ var fontSize = localStorage.fontSize = localStorage.fontSize || 16;
+ if (fontSize != 10 && fontSize != 16 && fontSize != 18 && fontSize != 32) {
+ localStorage.fontSize = 16;
+ }
+ localStorage.highlightMode = photoshop.flag =
+ localStorage.highlightMode || 'rectangle';
+ localStorage.highlightColor = localStorage.highlightColor || '#FF0000';
+ localStorage.fontColor = localStorage.fontColor || '#FF0000';
+ localStorage.highlightType = photoshop.highlightType =
+ localStorage.highlightType || 'border';
+ localStorage.blackoutType = localStorage.blackoutType || 'redact';
+ localStorage.lineType = localStorage.lineType || 'line';
+ localStorage.lineColor = localStorage.lineColor || '#FF0000';
+ photoshop.setHighlightColorBoxStyle(localStorage.highlightColor);
+ $('fontColorBox').style.backgroundColor =
+ localStorage.fontColor || '#FF0000';
+ $('btnHighlight').addEventListener('click', function() {
+ photoshop.toDo(this, localStorage.highlightMode);
+ photoshop.setHighlightColorBoxStyle(localStorage.highlightColor);
+ }, false);
+
+ $('btnBlackout').addEventListener('click', function() {
+ photoshop.toDo(this, localStorage.blackoutType);
+ photoshop.setBlackoutColorBoxStyle();
+ }, false);
+
+ $('btnText').addEventListener('click', function() {
+ photoshop.toDo(this, 'text');
+ }, false);
+
+ $('btnLine').addEventListener('click', function() {
+ photoshop.toDo(this, localStorage.lineType);
+ photoshop.setLineColorBoxStyle();
+ }, false);
+
+
+
+ photoshop.setHighlightColorBoxStyle(localStorage.highlightColor);
+ $('borderMode').addEventListener('click', function() {
+ localStorage.highlightType = 'border';
+ }, false);
+ $('rectMode').addEventListener('click', function() {
+ localStorage.highlightType = 'rect';
+ }, false);
+ $('rectangle').addEventListener('click', function() {
+ localStorage.highlightMode = photoshop.flag = 'rectangle';
+ photoshop.markCurrentElement(this);
+ }, false);
+ $('radiusRectangle').addEventListener('click', function() {
+ localStorage.highlightMode = photoshop.flag = 'radiusRectangle';
+ photoshop.markCurrentElement(this);
+ }, false);
+ $('ellipse').addEventListener('click', function() {
+ localStorage.highlightMode = photoshop.flag = 'ellipse';
+ photoshop.markCurrentElement(this);
+ }, false);
+ photoshop.setBlackoutColorBoxStyle();
+ $('redact').addEventListener('click', function() {
+ localStorage.blackoutType = 'redact';
+ }, false);
+ $('blur').addEventListener('click', function() {
+ localStorage.blackoutType = 'blur';
+ }, false);
+
+ photoshop.setLineColorBoxStyle();
+
+ photoshop.createColorPadStr($('highlightColorPad'), 'highlight');
+ photoshop.createColorPadStr($('fontColorPad'), 'text');
+ photoshop.createColorPadStr($('lineColorPad'), 'line');
+
+ $('straightLine').addEventListener('click', function() {
+ localStorage.lineType = 'line';
+ photoshop.setLineColorBoxStyle();
+ }, false);
+ $('arrow').addEventListener('click', function() {
+ localStorage.lineType = 'arrow';
+ photoshop.setLineColorBoxStyle();
+ }, false);
+
+ photoshop.setFontSize(localStorage.fontSize);
+ $('size_10').addEventListener('click', function() {
+ photoshop.setFontSize(10);
+ }, false);
+ $('size_16').addEventListener('click', function() {
+ photoshop.setFontSize(16);
+ }, false);
+ $('size_18').addEventListener('click', function() {
+ photoshop.setFontSize(18);
+ }, false);
+ $('size_32').addEventListener('click', function() {
+ photoshop.setFontSize(32);
+ }, false);
+ },
+
+ drawEllipseOnMaskCanvas: function(endX, endY, type, layerId) {
+ var ctx = $('mask-canvas').getContext('2d');
+ ctx.clearRect(0, 0, $('mask-canvas').width, $('mask-canvas').height);
+ var x = (photoshop.startX + endX) / 2;
+ var y = (photoshop.startY + endY) / 2;
+ var xAxis = Math.abs(endX - photoshop.startX) / 2;
+ var yAxis = Math.abs(endY - photoshop.startY) / 2;
+ canvas.drawEllipse(ctx, photoshop.color, x, y, xAxis, yAxis, 3,
+ photoshop.highlightType);
+ if (type == 'end') {
+ var offsetLeft = parseInt($(layerId).style.left);
+ var offsetTop = parseInt($(layerId).style.top);
+ var startX = photoshop.startX - offsetLeft ;
+ var startY = photoshop.startY - offsetTop ;
+ var newEndX = photoshop.endX - offsetLeft ;
+ var newEndY = photoshop.endY - offsetTop ;
+ x = (startX + newEndX) / 2;
+ y = (startY + newEndY) / 2;
+ xAxis = Math.abs(newEndX - startX) / 2;
+ yAxis = Math.abs(newEndY - startY) / 2;
+ var cavCopy = photoshop.createCanvas(layerId);
+ cavCopy.width = Math.abs(endX - photoshop.startX);
+ cavCopy.height = Math.abs(endY - photoshop.startY);
+ var ctxCopy = cavCopy.getContext('2d');
+ canvas.drawEllipse(ctxCopy, photoshop.color, x, y,
+ xAxis, yAxis, 3, photoshop.highlightType);
+ ctx.clearRect(0, 0, $('mask-canvas').width, $('mask-canvas').height);
+ }
+ },
+
+ showTip: function(className, message, delay) {
+ delay = delay || 2000;
+ var div = document.createElement('div');
+ div.className = className;
+ div.innerHTML = message;
+ document.body.appendChild(div);
+ div.style.left = (document.body.clientWidth - div.clientWidth) / 2 + 'px';
+ window.setTimeout(function() {
+ document.body.removeChild(div);
+ }, delay);
+ }
+};
+
+photoshop.init();
+$('photo').addEventListener('mousemove', photoshop.onMouseMove, true);
+$('photo').addEventListener('mousedown', photoshop.onMouseDown, true);
+$('photo').addEventListener('mouseup', photoshop.onMouseUp, true);
+document.addEventListener('mouseup', photoshop.onMouseUp, true);
+document.addEventListener('mousemove', photoshop.onMouseMove, true);
+
+$('canvas').addEventListener(
+ 'selectstart', function f(e) { return false });
+$('mask-canvas').addEventListener(
+ 'selectstart', function f(e) { return false });
+$('btnClose').addEventListener('click', photoshop.closeCurrentTab);
+$('uploadAccountList').addEventListener('click', function(e) {
+ var target = e.target;
+ var classList = Array.prototype.slice.call(target.classList)
+ if (classList.indexOf('accountName') >= 0) {
+ var site = target.dataset.site;
+ var userId = target.dataset.userId;
+ UploadUI.upload(site, userId);
+ } else if (classList.indexOf('deleteBtn') >= 0) {
+ var accountId = target.dataset.accountId;
+ UploadUI.deleteAccountItem(accountId);
+ }
+});
diff --git a/src/js/sina_microblog.js b/src/js/sina_microblog.js
new file mode 100644
index 0000000..18b153b
--- /dev/null
+++ b/src/js/sina_microblog.js
@@ -0,0 +1,122 @@
+const APPKEY = '1350884563';
+const AUTHORIZE_URL = 'https://api.weibo.com/oauth2/authorize';
+const REDIRECT_URL = 'https://api.weibo.com/oauth2/default.html'
+const SINA_USER_INFO_URL = 'https://api.weibo.com/2/users/show.json';
+const SINA_PHOTO_UPLOAD_URL = 'https://upload.api.weibo.com/2/statuses/upload.json';
+const SINA_LOGOUT_URL = 'https://api.weibo.com/2/account/end_session.json';
+
+var SinaMicroblog = {
+ siteId: 'sina',
+ currentUserId: null,
+ accessTokenCallback: null,
+
+ isRedirectUrl: function() {},
+
+ getAccessToken: function(callback) {
+ SinaMicroblog.accessTokenCallback = callback;
+ var url = AUTHORIZE_URL + '?client_id=' + APPKEY +
+ '&redirect_uri=' + REDIRECT_URL + '&response_type=token';
+ chrome.tabs.create({url: url});
+ },
+
+ parseAccessToken: function(url) {
+ var result = 'failure';
+ var msgOrUser = 'sina_failed_to_get_access_token';
+ var hash = url.split('#')[1];
+ if (hash && typeof hash == 'string') {
+ var keyValues = hash.split('&');
+ var response = {};
+
+ for (var keyValue, i = 0, l = keyValues.length; i < l; i++) {
+ keyValue = keyValues[i].split('=');
+ response[keyValue[0]] = keyValue[1];
+ }
+
+ if (!response.error && response.access_token && response.uid) {
+ result = 'success';
+ msgOrUser = new User({
+ id: response.uid,
+ accessToken: response.access_token
+ });
+ }
+ }
+
+ SinaMicroblog.accessTokenCallback(result, msgOrUser);
+ SinaMicroblog.accessTokenCallback = null;
+ },
+
+ getUserInfo: function(user, callback) {
+ ajax({
+ url: SINA_USER_INFO_URL,
+ parameters: {
+ access_token: user.accessToken,
+ uid: user.id
+ },
+ success: function(data) {
+ if (callback) {
+ user.name = data.screen_name;
+ callback('success', user);
+ }
+ },
+ status: {
+ others: function(data) {
+ callback('failure', 'failed_to_get_user_info');
+ }
+ }
+ });
+ },
+
+ upload: function(user, caption, imageData, callback) {
+ caption = encodeURIComponent(caption);
+ var params = {
+ access_token: user.accessToken,
+ status: caption
+ };
+ var binaryData = {
+ boundary: MULTIPART_FORMDATA_BOUNDARY,
+ name: 'pic',
+ value: 'test.png',
+ data: imageData,
+ type: 'image/png'
+ };
+
+ ajax({
+ url: SINA_PHOTO_UPLOAD_URL,
+ parameters: params,
+ multipartData: binaryData,
+ success: function(microblog) {
+ callback('success', microblog.id);
+ },
+ status: {
+ others: function(res) {
+ var message = 'failed_to_upload_image';
+ var errorCode = res.error_code;
+ var invalidAccessTokenCodes =
+ [21332, 21314, 21315, 21316, 21317, 21319, 21327];
+ if (invalidAccessTokenCodes.indexOf(errorCode) >= 0) {
+ message = 'bad_access_token';
+ }
+ callback('failure', message);
+ }
+ }
+ });
+ },
+
+ getPhotoLink: function(user, microblogId, callback) {
+ var photoLink = 'http://weibo.com/' + user.id + '/profile';
+ callback('success', photoLink);
+ },
+
+ logout: function(callback) {
+ var params = {source: APPKEY};
+ ajax({
+ url: SINA_LOGOUT_URL,
+ parameters: params,
+ complete: function(statusCode, data) {
+ // Response status 403 means no user signed in
+ if ((statusCode == 200 || statusCode == 403) && callback)
+ callback(data);
+ }
+ });
+ }
+};
\ No newline at end of file
diff --git a/src/js/site.js b/src/js/site.js
new file mode 100644
index 0000000..6bb43f8
--- /dev/null
+++ b/src/js/site.js
@@ -0,0 +1,63 @@
+var Site = function(id) {
+ this.siteId = id;
+};
+
+Site.prototype = {
+ /**
+ * Get access token by user's authorization.
+ * @param {Function} callback call-back function, parameters:
+ * {String} result, success or failure; {User|String} user with access
+ * token, etc. or error message.
+ */
+ getAccessToken: function(callback) {},
+
+ /**
+ * Check if the url is redirect url for retrieving access token of current
+ * site.
+ * @param {String} url
+ * @return {Boolean} result
+ */
+ isRedirectUrl: function(url) {},
+
+ /**
+ * Parse and get access token from redirect url, then call call-back function
+ * of passed by calling getAccessToken method with access token.
+ * @param {String} url
+ */
+ parseAccessToken: function(url) {},
+
+ /**
+ * Get user information.
+ * @param {User} user
+ * @param {Function} callback call-back function, parameters:
+ * {String} result, success or failure; {User|String} user with user id,
+ * user name, etc. or error message.
+ */
+ getUserInfo: function(user, callback) {},
+
+ /**
+ * Upload image.
+ * @param {User} user user data with access token, etc.
+ * @param {String} caption image description
+ * @param {String} imageData binary image data
+ * @param callback call-back function, parameters:
+ * {String} result, success or failure; {String} photo id or error message.
+ */
+ upload: function(user, caption, imageData, callback) {},
+
+ /**
+ * Get photo link.
+ * @param {User} user user data with id, access token, etc.
+ * @param {String} photoId
+ * @param {Function} callback call-back function, parameters:
+ * {String} result, success or failure; {String} photo link or error
+ * message.
+ */
+ getPhotoLink: function(user, photoId, callback) {},
+
+ /**
+ * Log out current signed in user.
+ * @param callback
+ */
+ logout: function(callback) {}
+};
\ No newline at end of file
diff --git a/src/js/ui.js b/src/js/ui.js
new file mode 100644
index 0000000..e13ea13
--- /dev/null
+++ b/src/js/ui.js
@@ -0,0 +1,70 @@
+var UI = {
+
+ show: function(element) {
+ if (UI.getStyle(element, 'display') == 'none') {
+ // Set display value to be defined by style sheet
+ var cssRules = window.getMatchedCSSRules(element, '', true);
+ var ruleLength = cssRules.length;
+ var display;
+ for (var i = ruleLength - 1; i >= 0 ; --i) {
+ display = cssRules[i].style.display;
+ if (display && display != 'none') {
+ element.style.display = display;
+ return;
+ }
+ }
+
+ // Set display value to be UA default value
+ var tmpElement = document.createElement(element.nodeName);
+ document.body.appendChild(tmpElement);
+ display = UI.getStyle(tmpElement, 'display');
+ document.body.removeChild(tmpElement);
+ element.style.display = display;
+ }
+ },
+
+ hide: function(element) {
+ element.style.display = 'none';
+ },
+
+ setStyle: function(element) {
+ var argLength = arguments.length;
+ var arg1 = arguments[1];
+ if (argLength == 2 && arg1.constructor == Object) {
+ for (var prop in arg1) {
+ var camelCasedProp = prop.replace(/-([a-z])/gi, function(n, letter) {
+ return letter.toUpperCase();
+ });
+ element.style[camelCasedProp] = arg1[prop];
+ }
+ } else if (argLength == 3)
+ element.style[arg1] = arguments[2];
+ },
+
+ getStyle: function(element, property) {
+ return window.getComputedStyle(element)[property];
+ },
+
+ addClass: function(element, className) {
+ var classes = element.className.split(' ');
+ classes.push(className);
+ element.className = classes.join(' ');
+ },
+
+ removeClass: function(element, className) {
+ var classes = element.className.split(' ');
+ var index = classes.indexOf(className);
+ if (index >= 0) {
+ classes.splice(index, 1);
+ element.className = classes.join(' ');
+ }
+ },
+
+ addStyleSheet: function(path) {
+ var link = document.createElement('link');
+ link.setAttribute('type', 'text/css');
+ link.setAttribute('rel', 'stylesheet');
+ link.setAttribute('href', path);
+ document.head.appendChild(link);
+ }
+};
\ No newline at end of file
diff --git a/src/js/upload_ui.js b/src/js/upload_ui.js
new file mode 100644
index 0000000..5b073f5
--- /dev/null
+++ b/src/js/upload_ui.js
@@ -0,0 +1,487 @@
+const CURRENT_LOCALE = chrome.i18n.getMessage('@@ui_locale');
+const MULTIPART_FORMDATA_BOUNDARY = 'Google_Chrome_Screen_Capture';
+const HIDE_ERROR_INFO_DELAY_TIME = 5000;
+
+var UploadUI = {
+ currentSite: '',
+ uploading: false,
+ sites: {},
+
+ registerSite: function(id, siteObject) {
+ this.sites[id] = siteObject;
+ },
+
+ getSiteObject: function(id) {
+ return this.sites[id];
+ },
+
+ setUploading: function(state) {
+ UploadUI.uploading = state;
+ },
+
+ init: function() {
+ // Register supported site for image sharing.
+ UploadUI.registerSite(SinaMicroblog.siteId, SinaMicroblog);
+ UploadUI.registerSite(Facebook.siteId, Facebook);
+ UploadUI.registerSite(Picasa.siteId, Picasa);
+ UploadUI.registerSite(Imgur.siteId, Imgur);
+
+ // Import style sheet for current locale
+ if (CURRENT_LOCALE == 'zh_CN')
+ UI.addStyleSheet('./i18n_styles/zh_CN_upload_image.css');
+ else
+ UI.addStyleSheet('./i18n_styles/en_US_upload_image.css');
+
+ // Get i18n message
+ i18nReplace('shareToSinaMicroblogText', SinaMicroblog.siteId +
+ '_upload_header');
+ i18nReplace('shareToFacebookText', Facebook.siteId + '_upload_header');
+ i18nReplace('shareToPicasaText', Picasa.siteId + '_upload_header');
+ i18nReplace('lastStep', 'return_to_site_selection');
+ i18nReplace('closeUploadWrapper', 'close_upload_wrapper');
+ i18nReplace('imageCaptionText', 'image_caption');
+ i18nReplace('photoSizeTip', 'photo_size_tip');
+ i18nReplace('shareToImgurText', Imgur.siteId + '_upload_header');
+ $('requiredFlag').setAttribute('title',
+ chrome.i18n.getMessage('invalid_caption'));
+
+ // Add event listeners
+ //$('btnUpload').addEventListener('click', UploadUI.showUploadWrapper, false);
+ $('btnSave').addEventListener('click', UploadUI.saveImage, false);
+ $('closeUploadWrapper').addEventListener('click',
+ UploadUI.hideUploadWrapper, false);
+
+ $('picasaBtn').addEventListener('click', function() {
+ UploadUI.showUploadContentWrapper(Picasa.siteId);
+ });
+ $('facebookBtn').addEventListener('click', function() {
+ UploadUI.showUploadContentWrapper(Facebook.siteId);
+ }, false);
+ $('sinaMicroblogBtn').addEventListener('click', function() {
+ UploadUI.showUploadContentWrapper(SinaMicroblog.siteId);
+ }, false);
+ $('imgurBtn').addEventListener('click', function() {
+ UploadUI.showUploadContentWrapper(Imgur.siteId);
+ }, false);
+ $('shareToOtherAccount').addEventListener('click', function() {
+ var currentSite = UploadUI.currentSite;
+
+ // Validate image description first
+ if (UploadUI.validatePhotoDescription(currentSite)) {
+ var callback = function() {
+ var authenticationTip =
+ chrome.i18n.getMessage('user_authentication_tip');
+ UploadUI.showAuthenticationProgress(authenticationTip);
+ UploadUI.getAccessToken(currentSite);
+ };
+ var users = Account.getUsers(currentSite);
+ var numberOfUsers = Object.keys(users).length;
+
+ // Logout when user has authenticated app
+ if (numberOfUsers) {
+ var logoutTip = chrome.i18n.getMessage('user_logout_tip');
+ UploadUI.showAuthenticationProgress(logoutTip);
+ var site = UploadUI.getSiteObject(currentSite);
+ site.logout(callback);
+ } else {
+ callback();
+ }
+ }
+ }, false);
+ $('lastStep').addEventListener('click', UploadUI.showUploadSitesWrapper,
+ false);
+ },
+
+ showUploadWrapper: function() {
+ var uploadWrapper = $('uploadWrapper');
+ UI.show(uploadWrapper);
+
+ // Reset upload wrapper position
+ var viewportWidth = window.innerWidth;
+ var viewportHeight = window.innerHeight;
+ var wrapperWidth = uploadWrapper.offsetWidth;
+ var wrapperHeight = uploadWrapper.offsetHeight;
+
+ var left = (viewportWidth - wrapperWidth) / 2;
+ var top = (viewportHeight - wrapperHeight) / 3;
+ left = left < 0 ? 0 : left;
+ top = top < 0 ? 0 : top;
+
+ var scrollTop = document.body.scrollTop;
+ var scrollLeft = document.body.scrollLeft;
+
+ UI.setStyle(uploadWrapper, {
+ top: top + scrollTop + 'px',
+ left: left + scrollLeft + 'px'
+ });
+ UploadUI.showUploadSitesWrapper();
+ UploadUI.showOverlay();
+ },
+
+ hideUploadWrapper: function() {
+ UI.hide($('uploadWrapper'));
+ UploadUI.hideOverlay();
+ },
+
+ showOverlay: function() {
+ var overlay = $('overlay');
+ UI.setStyle(overlay, {
+ width: document.body.scrollWidth + 'px',
+ height: document.body.scrollHeight + 'px'
+ });
+ UI.show($('overlay'));
+ },
+
+ hideOverlay: function() {
+ UI.hide($('overlay'));
+ },
+
+ updateUploadHeader: function(title) {
+ $('uploadHeader').firstElementChild.firstElementChild.innerText = title;
+ },
+
+ showUploadSitesWrapper: function() {
+ var uploadHeader = chrome.i18n.getMessage('upload_sites_header');
+ UploadUI.updateUploadHeader(uploadHeader);
+ UI.show($('uploadSitesWrapper'));
+ UploadUI.hideUploadContentWrapper();
+ UI.hide($('lastStep'));
+ },
+
+ hideUploadSitesWrapper: function() {
+ UI.hide($('uploadSitesWrapper'));
+ },
+
+ showUploadContentWrapper: function(site) {
+ UploadUI.currentSite = site;
+
+ // Update upload wrapper UI
+ var uploadHeader = chrome.i18n.getMessage(site + '_upload_header');
+ UploadUI.updateUploadHeader(uploadHeader);
+ UploadUI.hideUploadSitesWrapper();
+ UploadUI.hideErrorInfo();
+ UploadUI.hideAuthenticationProgress();
+ UploadUI.clearPhotoDescription();
+ UI.show($('uploadContentWrapper'));
+ UI.show($('lastStep'));
+ UploadUI.updateShareToOtherAccountText(site);
+ UploadUI.togglePhotoDescriptionRequiredFlag(site);
+
+ // Show authenticated accounts of current site
+ UploadUI.clearAccounts();
+ var users = Account.getUsers(site);
+ for (var userId in users) {
+ UploadUI.addAuthenticatedAccount(site, userId);
+ }
+ },
+
+ hideUploadContentWrapper: function() {
+ UI.hide($('uploadContentWrapper'));
+ },
+
+ clearPhotoDescription: function() {
+ $('imageCaption').value = '';
+ },
+
+ validatePhotoDescription: function(site) {
+ var caption = $('imageCaption');
+ var invalidCaptionMsg = chrome.i18n.getMessage('invalid_caption');
+
+ // Validate photo description
+ if (site == SinaMicroblog.siteId && caption.value == '') {
+ UploadUI.showErrorInfo(invalidCaptionMsg);
+ caption.focus();
+ return false;
+ }
+ return true;
+ },
+
+ togglePhotoDescriptionRequiredFlag: function(siteId) {
+ if (siteId == SinaMicroblog.siteId)
+ UI.show($('requiredFlag'));
+ else
+ UI.hide($('requiredFlag'));
+ },
+
+ updateShareToOtherAccountText: function(siteId) {
+ var users = Account.getUsers(siteId);
+ var userLength = Object.keys(users).length;
+ if (userLength)
+ i18nReplace('shareToOtherAccount', 'share_to_other_account');
+ else
+ i18nReplace('shareToOtherAccount', 'share_to_' + siteId + '_account');
+ },
+
+ showErrorInfo: function(text) {
+ UI.show($('errorWrapper'));
+ $('errorInfo').innerHTML = text;
+ setTimeout(function() {
+ UploadUI.hideErrorInfo();
+ }, HIDE_ERROR_INFO_DELAY_TIME);
+ },
+
+ hideErrorInfo: function() {
+ UI.hide($('errorWrapper'));
+ },
+
+ showProgressBar: function(accountId) {
+ var progress = document.querySelector('#' + accountId +
+ ' .progressBar');
+ UI.show(progress);
+ },
+
+ hideProgressBar: function(accountId) {
+ var progress = document.querySelector('#' + accountId +
+ ' .progressBar');
+ UI.hide(progress);
+ },
+
+ showAuthenticationProgress: function(title) {
+ var progress = $('authenticationProgress');
+ progress.setAttribute('title', title);
+ UI.show(progress);
+ },
+
+ hideAuthenticationProgress: function() {
+ UI.hide($('authenticationProgress'));
+ },
+
+ setProgress: function(accountId, loaded, total) {
+ console.log('In setProgress, loaded: ' + loaded + ', total: ' + total);
+ var progress = document.querySelector('#' + accountId + ' .progressBar');
+
+ // One progress bar has 4 parts to represent progress
+ var level = parseInt(loaded / total / 0.25);
+ UI.setStyle(progress, 'background-position-y', '-' + (12 * level) + 'px');
+ },
+
+ showPhotoLink: function(accountId, link) {
+ var photoLink = document.querySelector('#' + accountId + ' .photoLink');
+ photoLink.setAttribute('href', link);
+ UI.setStyle(photoLink, 'display', 'inline');
+ },
+
+ hidePhotoLink: function(accountId) {
+ var photoLink = document.querySelector('#' + accountId + ' .photoLink');
+ UI.hide(photoLink);
+ },
+
+ showUploadInfo: function(accountId, text) {
+ var uploadInfo = document.querySelector('#' + accountId + ' .uploadInfo');
+ uploadInfo.innerHTML = text;
+ UI.show(uploadInfo);
+ },
+
+ hideUploadInfo: function(accountId) {
+ var uploadInfo = document.querySelector('#' + accountId + ' .uploadInfo');
+ UI.hide(uploadInfo);
+ },
+
+ clearAccounts: function() {
+ $('uploadAccountList').innerHTML = '';
+ },
+
+ addAuthenticatedAccount: function(site, userId) {
+ var template = $('accountItemTemplate').innerHTML;
+
+ // Replace i18n message
+ template = template.replace(/\$\{accountId\}/gi, site + '_' + userId);
+ var shareToText = chrome.i18n.getMessage('share_to');
+ template = template.replace(/\$\{accountName\}/gi,
+ shareToText + ' ' + Account.getUser(site, userId)['name']);
+ template = template.replace('${site}', site);
+ template = template.replace('${userId}', userId);
+ template = template.replace(/\$\{deletionTitle\}/gi,
+ chrome.i18n.getMessage('deletion_title'));
+ template = template.replace(/\$\{photoLinkText\}/gi,
+ chrome.i18n.getMessage('photo_link_text'));
+ template = template.replace(/\$\{progressInfo\}/gi,
+ chrome.i18n.getMessage('progress_info'));
+
+ // At most show 3 authenticated users
+ var uploadAccountList = $('uploadAccountList');
+ var accountsNumber = uploadAccountList.childElementCount;
+ if (accountsNumber == 2) {
+ uploadAccountList.removeChild(uploadAccountList.lastElementChild);
+ }
+ uploadAccountList.innerHTML = template + uploadAccountList.innerHTML;
+
+ $('accountName').addEventListener('click', function(e) {
+ UploadUI.upload(site, userId);
+ });
+ $('deleteBtn').addEventListener('click', function(e) {
+ e.stopPropagation();
+ UploadUI.deleteAccountItem(site + '_' + userId);
+ });
+
+ UploadUI.updateShareToOtherAccountText(site);
+ },
+
+ deleteAccountItem: function(accountId, noConfirm) {
+ if (UploadUI.uploading && !noConfirm)
+ return;
+ var confirmText = chrome.i18n.getMessage('account_deletion_confirm');
+ if (noConfirm || confirm(confirmText)) {
+ $('uploadAccountList').removeChild($(accountId));
+
+ // Clear localStorage
+ var site = accountId.split('_')[0];
+ var userId = accountId.split('_')[1];
+ Account.removeUser(site, userId);
+ UploadUI.updateShareToOtherAccountText(site);
+ }
+ },
+
+ upload: function(siteId, userId) {
+ if (UploadUI.uploading)
+ return;
+
+ // Initialize UI
+ var accountId = siteId + '_' + userId;
+ UploadUI.hideErrorInfo();
+ UploadUI.hideUploadInfo(accountId);
+ UploadUI.hidePhotoLink(accountId);
+ if (!UploadUI.validatePhotoDescription(siteId))
+ return;
+ var caption = $('imageCaption').value;
+
+ // Get ready for upload image.
+ photoshop.draw();
+ UploadUI.setUploading(true);
+ UploadUI.showProgressBar(accountId);
+
+ var site = UploadUI.getSiteObject(siteId);
+ var user = Account.getUser(siteId, userId);
+ var imageData = UploadUI.getImageData();
+ var infoText;
+
+ var callback = function(result, photoIdOrMessage) {
+ if (result == 'success') {
+ infoText = chrome.i18n.getMessage('get_photo_link');
+ UploadUI.showUploadInfo(accountId, infoText);
+ site.getPhotoLink(user, photoIdOrMessage, function(photoLinkResult,
+ photoLinkOrMessage) {
+ if (photoLinkResult == 'success') {
+ UploadUI.setUploading(false);
+ UploadUI.hideUploadInfo(accountId);
+ UploadUI.showPhotoLink(accountId, photoLinkOrMessage);
+ } else {
+ UploadUI.showErrorInfo(photoLinkOrMessage);
+ }
+ });
+ } else {
+ if (photoIdOrMessage == 'bad_access_token' ||
+ photoIdOrMessage == 'invalid_album_id') {
+ Account.removeUser(site.siteId, site.currentUserId);
+ UploadUI.deleteAccountItem(accountId, true);
+ UploadUI.getAccessToken(siteId);
+ }
+ UploadUI.setUploading(false);
+ UploadUI.hideProgressBar(accountId);
+ UploadUI.showErrorInfo(chrome.i18n.getMessage(photoIdOrMessage));
+ }
+ UploadUI.hideProgressBar(accountId);
+ };
+
+ if (user) {
+ site.currentUserId = user.id;
+ site.upload(user, caption, imageData, callback);
+ } else {
+ UploadUI.getAccessToken(siteId);
+ }
+ },
+
+ getAccessToken: function(siteId) {
+ var site = UploadUI.getSiteObject(siteId);
+ var accessTokenCallback = function(result, userOrMessage) {
+ if (result == 'success') {
+ UploadUI.getUserInfo(siteId, userOrMessage);
+ } else {
+ // Show error information according to error reason
+ UploadUI.showErrorInfo(chrome.i18n.getMessage(userOrMessage));
+ UploadUI.hideAuthenticationProgress();
+ }
+ };
+
+ site.getAccessToken(accessTokenCallback);
+ },
+
+ getUserInfo: function(siteId, user) {
+ var site = UploadUI.getSiteObject(siteId);
+ site.getUserInfo(user, function(result, userOrMessage) {
+ if (result == 'success') {
+ var userId = user.id;
+ // Check if the authenticated user is added.
+ if (!Account.getUser(siteId, userId)) {
+ site.currentUserId = userId;
+ Account.addUser(siteId, user);
+ UploadUI.addAuthenticatedAccount(siteId, userId);
+ }
+ UploadUI.upload(siteId, userId);
+ } else {
+ var msg = chrome.i18n.getMessage(userOrMessage);
+ UploadUI.showErrorInfo(msg);
+ }
+ UploadUI.hideAuthenticationProgress();
+ });
+ },
+
+ getImageData: function() {
+ var dataUrl = $('canvas').toDataURL('image/png');
+ var imageDataIndex = dataUrl.indexOf('data:image/png;base64,');
+ if (imageDataIndex != 0) {
+ return;
+ }
+
+ // Decode to binary data
+ return atob(dataUrl.substr(imageDataIndex + 22));
+ },
+
+ saveImage: function() {
+ $('canvas').toBlob(function(blob) {
+ console.log(chrome.extension.getBackgroundPage());
+ saveAs(blob, chrome.extension.getBackgroundPage().screenshot.screenshotName+".png");
+ });
+ }
+};
+
+(function() {
+// Cache tab id of edit page, so that we can get tab focus after getting access
+// token.
+var tabIdOfEditPage;
+chrome.tabs.getSelected(null, function(tab) {
+ tabIdOfEditPage = tab.id;
+});
+
+function selectTab(tabId) {
+ chrome.tabs.update(tabId, {
+ selected: true
+ });
+}
+
+function closeTab(tabId) {
+ chrome.tabs.remove(tabId);
+}
+
+function parseAccessToken(senderId, url, siteId) {
+ var sites = UploadUI.sites;
+ for (var id in sites) {
+ var site = sites[id];
+ if ((siteId && id == siteId) || site.isRedirectUrl(url)) {
+ selectTab(tabIdOfEditPage);
+ closeTab(senderId);
+ site.parseAccessToken(url);
+ return true;
+ }
+ }
+ return false;
+}
+
+chrome.extension.onMessage.addListener(function(request, sender) {
+ switch (request.msg) {
+ case 'url_for_access_token':
+ parseAccessToken(sender.tab.id, request.url, request.siteId);
+ break;
+ }
+});
+})();