summaryrefslogtreecommitdiffstats
path: root/chrome/common/extensions/docs/examples/api/downloads
diff options
context:
space:
mode:
authorbenjhayden@chromium.org <benjhayden@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-30 15:20:46 +0000
committerbenjhayden@chromium.org <benjhayden@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-30 15:20:46 +0000
commit6d0aba64b4c5e150e9775bf8ef134a6f1690a90e (patch)
tree4400af5fac8fbc5c837ce00f40c892938b255fc0 /chrome/common/extensions/docs/examples/api/downloads
parentaaf76d62e23ba313fa96f3e1c044e7570dea478a (diff)
downloadchromium_src-6d0aba64b4c5e150e9775bf8ef134a6f1690a90e.zip
chromium_src-6d0aba64b4c5e150e9775bf8ef134a6f1690a90e.tar.gz
chromium_src-6d0aba64b4c5e150e9775bf8ef134a6f1690a90e.tar.bz2
Modern Download Manager browser action extension
Some of the features depend on https://codereview.chromium.org/16924017/ After that CL and this are submitted, I'll upload this to the webstore and start getting the By Google tag. If you have a canary/dev/beta channel chrome installed or have a binary built from ToT and you're on the google network, go to http://www.corp.google.com/~benjhayden/ right-click "download_manager.crx", Save Link As, open chrome://extensions and drag download_manager.crx from the shelf and drop it on chrome://extensions to install it. If you install it on the stable channel, it will invite you to install the beta channel. R=asanka@chromium.org, asargent@chromium.org, mkwst@chromium.org Review URL: https://codereview.chromium.org/17286017 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@214356 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common/extensions/docs/examples/api/downloads')
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/_locales/en/messages.json284
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/background.js260
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/icon128.pngbin0 -> 1415 bytes
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/icon19.pngbin0 -> 285 bytes
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/icon38.pngbin0 -> 394 bytes
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/icons.html10
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/icons.js12
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/manifest.json14
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.css229
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.html104
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.js713
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_open/_locales/en/messages.json9
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_open/background.js48
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_open/icon128.pngbin0 -> 1415 bytes
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_open/icon16.pngbin0 -> 285 bytes
-rw-r--r--chrome/common/extensions/docs/examples/api/downloads/download_open/manifest.json8
16 files changed, 1691 insertions, 0 deletions
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/_locales/en/messages.json b/chrome/common/extensions/docs/examples/api/downloads/download_manager/_locales/en/messages.json
new file mode 100644
index 0000000..cc1b636
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/_locales/en/messages.json
@@ -0,0 +1,284 @@
+{"extName": {
+ "message": "Modern Download Manager",
+ "description": "Extension name"},
+ "extDesc": {
+ "message": "Modern Download Manager User Interface for Google Chrome",
+ "description": "Extension description"},
+ "badChromeVersion": {
+ "message": "The downloads API is only available on the canary, dev, and beta channels.",
+ "description": ""},
+ "tabTitle": {
+ "message": "Downloads",
+ "description": "tab title"},
+ "searchPlaceholder": {
+ "message": "Search Downloads",
+ "description": ""},
+ "clearAllTitle": {
+ "message": "Erase All Visible Downloads",
+ "description": ""},
+ "openDownloadsFolderTitle": {
+ "message": "Open Downloads Folder",
+ "description": ""},
+ "zeroItems": {
+ "message": "There are zero download items.",
+ "description": ""},
+ "searching": {
+ "message": "Teleporting lots of goats...",
+ "description": ""},
+ "zeroSearchResults": {
+ "message": "Zero matches",
+ "description": ""},
+ "showOlderDownloads": {
+ "message": "Show Older Downloads",
+ "description": ""},
+ "loadingOlderDownloads": {
+ "message": "Loading Older Downloads...",
+ "description": ""},
+ "openTitle": {
+ "message": "Open",
+ "description": ""},
+ "pauseTitle": {
+ "message": "Pause",
+ "description": ""},
+ "resumeTitle": {
+ "message": "Resume",
+ "description": ""},
+ "cancelTitle": {
+ "message": "Cancel",
+ "description": ""},
+ "removeFileTitle": {
+ "message": "Remove file",
+ "description": ""},
+ "eraseTitle": {
+ "message": "Erase",
+ "description": ""},
+ "retryTitle": {
+ "message": "Retry",
+ "description": ""},
+ "referrerTitle": {
+ "message": "Referrer",
+ "description": ""},
+ "month0abbr": {"message": "Jan","description": ""},
+ "month1abbr": {"message": "Feb","description": ""},
+ "month2abbr": {"message": "Mar","description": ""},
+ "month3abbr": {"message": "Apr","description": ""},
+ "month4abbr": {"message": "May","description": ""},
+ "month5abbr": {"message": "Jun","description": ""},
+ "month6abbr": {"message": "Jul","description": ""},
+ "month7abbr": {"message": "Aug","description": ""},
+ "month8abbr": {"message": "Sep","description": ""},
+ "month9abbr": {"message": "Oct","description": ""},
+ "month10abbr": {"message": "Nov","description": ""},
+ "month11abbr": {"message": "Dec","description": ""},
+ "openWhenCompleteFinishing": {
+ "message": "Opening in just a moment",
+ "description": ""},
+ "timeLeftFinishing": {
+ "message": "finishing...",
+ "description": ""},
+ "openWhenCompleteDays": {
+ "message": "Opening in $days$d $hours$h",
+ "description": "",
+ "placeholders": {
+ "days": {
+ "content": "$1",
+ "example": "2"},
+ "hours": {
+ "content": "$2",
+ "example": "23"}}},
+ "timeLeftDays": {
+ "message": "$days$d $hours$h left",
+ "description": "",
+ "placeholders": {
+ "days": {
+ "content": "$1",
+ "example": "2"},
+ "hours": {
+ "content": "$2",
+ "example": "23"}}},
+ "openWhenCompleteHours": {
+ "message": "Opening in $hours$h $mins$m",
+ "description": "",
+ "placeholders": {
+ "hours": {
+ "content": "$1",
+ "example": "23"},
+ "mins": {
+ "content": "$2",
+ "example": "59"}}},
+ "timeLeftHours": {
+ "message": "$hours$h $mins$m left",
+ "description": "",
+ "placeholders": {
+ "hours": {
+ "content": "$1",
+ "example": "23"},
+ "mins": {
+ "content": "$2",
+ "example": "59"}}},
+ "openWhenCompleteMinutes": {
+ "message": "Opening in $mins$m $sec$s",
+ "description": "",
+ "placeholders": {
+ "mins": {
+ "content": "$1",
+ "example": "59"},
+ "sec": {
+ "content": "$2",
+ "example": "59"}}},
+ "timeLeftMinutes": {
+ "message": "$mins$m $sec$s left",
+ "description": "",
+ "placeholders": {
+ "mins": {
+ "content": "$1",
+ "example": "59"},
+ "sec": {
+ "content": "$2",
+ "example": "59"}}},
+ "openWhenCompleteSeconds": {
+ "message": "Opening in $sec$s",
+ "description": "",
+ "placeholders": {
+ "sec": {
+ "content": "$1",
+ "example": "59"}}},
+ "timeLeftSeconds": {
+ "message": "$sec$s left",
+ "description": "",
+ "placeholders": {
+ "sec": {
+ "content": "$1",
+ "example": "59"}}},
+ "error_FILE_FAILED": {
+ "message": "File Failed",
+ "description": ""},
+ "error_FILE_ACCESS_DENIED": {
+ "message": "File-System Access Denied",
+ "description": ""},
+ "error_FILE_NO_SPACE": {
+ "message": "No Space On Disk",
+ "description": ""},
+ "error_FILE_NAME_TOO_LONG": {
+ "message": "Filename Too Long",
+ "description": ""},
+ "error_FILE_TOO_LARGE": {
+ "message": "File Too Large",
+ "description": ""},
+ "error_FILE_VIRUS_INFECTED": {
+ "message": "Virus Infected",
+ "description": ""},
+ "error_FILE_TRANSIENT_ERROR": {
+ "message": "Transient File-System Error",
+ "description": ""},
+ "error_FILE_BLOCKED": {
+ "message": "File Blocked",
+ "description": ""},
+ "error_FILE_SECURITY_CHECK_FAILED": {
+ "message": "Security Check Failed",
+ "description": ""},
+ "error_FILE_TOO_SHORT": {
+ "message": "File Too Short",
+ "description": ""},
+ "error_NETWORK_FAILED": {
+ "message": "Network Failure",
+ "description": ""},
+ "error_NETWORK_TIMEOUT": {
+ "message": "Network Timeout",
+ "description": ""},
+ "error_NETWORK_DISCONNECTED": {
+ "message": "Network Disconnected",
+ "description": ""},
+ "error_NETWORK_SERVER_DOWN": {
+ "message": "Server Down",
+ "description": ""},
+ "error_SERVER_FAILED": {
+ "message": "Server Failure",
+ "description": ""},
+ "error_SERVER_NO_RANGE": {
+ "message": "Server No Range",
+ "description": ""},
+ "error_SERVER_PRECONDITION": {
+ "message": "Server Precondition Failure",
+ "description": ""},
+ "error_SERVER_BAD_CONTENT": {
+ "message": "Bad Content",
+ "description": ""},
+ "error_USER_CANCELED": {
+ "message": "Cancelled",
+ "description": ""},
+ "error_USER_SHUTDOWN": {
+ "message": "Cancelled",
+ "description": ""},
+ "error_CRASH": {
+ "message": "Crash",
+ "description": ""},
+ "error_1": {
+ "message": "File Failed",
+ "description": ""},
+ "error_2": {
+ "message": "File-System Access Denied",
+ "description": ""},
+ "error_3": {
+ "message": "No Space On Disk",
+ "description": ""},
+ "error_5": {
+ "message": "Filename Too Long",
+ "description": ""},
+ "error_6": {
+ "message": "File Too Large",
+ "description": ""},
+ "error_7": {
+ "message": "Virus Infected",
+ "description": ""},
+ "error_10": {
+ "message": "Transient File-System Error",
+ "description": ""},
+ "error_11": {
+ "message": "File Blocked",
+ "description": ""},
+ "error_12": {
+ "message": "Security Check Failed",
+ "description": ""},
+ "error_13": {
+ "message": "File Too Short",
+ "description": ""},
+ "error_20": {
+ "message": "Network Failure",
+ "description": ""},
+ "error_21": {
+ "message": "Network Timeout",
+ "description": ""},
+ "error_22": {
+ "message": "Network Disconnected",
+ "description": ""},
+ "error_23": {
+ "message": "Server Down",
+ "description": ""},
+ "error_30": {
+ "message": "Server Failure",
+ "description": ""},
+ "error_31": {
+ "message": "Server No Range",
+ "description": ""},
+ "error_32": {
+ "message": "Server Precondition Failure",
+ "description": ""},
+ "error_33": {
+ "message": "Bad Content",
+ "description": ""},
+ "error_40": {
+ "message": "Cancelled",
+ "description": ""},
+ "error_41": {
+ "message": "Cancelled",
+ "description": ""},
+ "error_50": {
+ "message": "Crash",
+ "description": ""},
+ "errorRemoved": {
+ "message": "Removed",
+ "description": ""},
+ "showInFolderTitle": {
+ "message": "Show in Folder",
+ "description": "Alt text for show in folder icon"}}
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/background.js b/chrome/common/extensions/docs/examples/api/downloads/download_manager/background.js
new file mode 100644
index 0000000..ffc2a4d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/background.js
@@ -0,0 +1,260 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var colors = {
+ progressColor: '#0d0',
+ arrow: '#555',
+ danger: 'red',
+ complete: 'green',
+ paused: 'grey',
+ background: 'white',
+};
+
+function drawLine(ctx, x1, y1, x2, y2) {
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.stroke();
+}
+
+function drawProgressSpinner(ctx, stage) {
+ var center = ctx.canvas.width/2;
+ var radius = center*0.9;
+ const segments = 16;
+ var segArc = 2 * Math.PI / segments;
+ ctx.lineWidth = Math.round(ctx.canvas.width*0.1);
+ for (var seg = 0; seg < segments; ++seg) {
+ var color = ((stage == 'm') ? ((seg % 2) == 0) : (seg <= stage));
+ ctx.fillStyle = ctx.strokeStyle = (
+ color ? colors.progressColor : colors.background);
+ ctx.moveTo(center, center);
+ ctx.beginPath();
+ ctx.arc(center, center, radius, (seg-4)*segArc, (seg-3)*segArc, false);
+ ctx.lineTo(center, center);
+ ctx.fill();
+ ctx.stroke();
+ }
+ ctx.strokeStyle = colors.background;
+ radius += ctx.lineWidth-1;
+ drawLine(ctx, center-radius, center, center+radius, center);
+ drawLine(ctx, center, center-radius, center, center+radius);
+ radius *= Math.sin(Math.PI/4);
+ drawLine(ctx, center-radius, center-radius, center+radius, center+radius);
+ drawLine(ctx, center-radius, center+radius, center+radius, center-radius);
+}
+
+function drawArrow(ctx) {
+ ctx.beginPath();
+ ctx.lineWidth = Math.round(ctx.canvas.width*0.1);
+ ctx.lineJoin = 'round';
+ ctx.strokeStyle = ctx.fillStyle = colors.arrow;
+ var center = ctx.canvas.width/2;
+ var minw2 = center*0.2;
+ var maxw2 = center*0.60;
+ var height2 = maxw2;
+ ctx.moveTo(center-minw2, center-height2);
+ ctx.lineTo(center+minw2, center-height2);
+ ctx.lineTo(center+minw2, center);
+ ctx.lineTo(center+maxw2, center);
+ ctx.lineTo(center, center+height2);
+ ctx.lineTo(center-maxw2, center);
+ ctx.lineTo(center-minw2, center);
+ ctx.lineTo(center-minw2, center-height2);
+ ctx.lineTo(center+minw2, center-height2);
+ ctx.stroke();
+ ctx.fill();
+}
+
+function drawDangerBadge(ctx) {
+ var s = ctx.canvas.width/100;
+ ctx.fillStyle = colors.danger;
+ ctx.strokeStyle = colors.background;
+ ctx.lineWidth = Math.round(s*5);
+ var edge = ctx.canvas.width-ctx.lineWidth;
+ ctx.beginPath();
+ ctx.moveTo(s*75, s*55);
+ ctx.lineTo(edge, edge);
+ ctx.lineTo(s*55, edge);
+ ctx.lineTo(s*75, s*55);
+ ctx.lineTo(edge, edge);
+ ctx.fill();
+ ctx.stroke();
+}
+
+function drawPausedBadge(ctx) {
+ var s = ctx.canvas.width/100;
+ ctx.beginPath();
+ ctx.strokeStyle = colors.background;
+ ctx.lineWidth = Math.round(s*5);
+ ctx.rect(s*55, s*55, s*15, s*35);
+ ctx.fillStyle = colors.paused;
+ ctx.fill();
+ ctx.stroke();
+ ctx.rect(s*75, s*55, s*15, s*35);
+ ctx.fill();
+ ctx.stroke();
+}
+
+function drawCompleteBadge(ctx) {
+ var s = ctx.canvas.width/100;
+ ctx.beginPath();
+ ctx.arc(s*75, s*75, s*15, 0, 2*Math.PI, false);
+ ctx.fillStyle = colors.complete;
+ ctx.fill();
+ ctx.strokeStyle = colors.background;
+ ctx.lineWidth = Math.round(s*5);
+ ctx.stroke();
+}
+
+function drawIcon(side, stage, badge) {
+ var canvas = document.createElement('canvas');
+ canvas.width = canvas.height = side;
+ document.body.appendChild(canvas);
+ var ctx = canvas.getContext('2d');
+ if (stage != 'n') {
+ drawProgressSpinner(ctx, stage);
+ }
+ drawArrow(ctx);
+ if (badge == 'd') {
+ drawDangerBadge(ctx);
+ } else if (badge == 'p') {
+ drawPausedBadge(ctx);
+ } else if (badge == 'c') {
+ drawCompleteBadge(ctx);
+ }
+ return canvas;
+}
+
+function maybeOpen(id) {
+ var openWhenComplete = [];
+ try {
+ openWhenComplete = JSON.parse(localStorage.openWhenComplete);
+ } catch (e) {
+ localStorage.openWhenComplete = JSON.stringify(openWhenComplete);
+ }
+ var openNowIndex = openWhenComplete.indexOf(id);
+ if (openNowIndex >= 0) {
+ chrome.downloads.open(id);
+ openWhenComplete.splice(openNowIndex, 1);
+ localStorage.openWhenComplete = JSON.stringify(openWhenComplete);
+ }
+}
+
+function setBrowserActionIcon(stage, badge) {
+ var canvas1 = drawIcon(19, stage, badge);
+ var canvas2 = drawIcon(38, stage, badge);
+ var imageData = {};
+ imageData['' + canvas1.width] = canvas1.getContext('2d').getImageData(
+ 0, 0, canvas1.width, canvas1.height);
+ imageData['' + canvas2.width] = canvas2.getContext('2d').getImageData(
+ 0, 0, canvas2.width, canvas2.height);
+ chrome.browserAction.setIcon({imageData:imageData});
+ canvas1.parentNode.removeChild(canvas1);
+ canvas2.parentNode.removeChild(canvas2);
+}
+
+function pollProgress() {
+ pollProgress.tid = -1;
+ chrome.downloads.search({}, function(items) {
+ var popupLastOpened = parseInt(localStorage.popupLastOpened);
+ var totalTotalBytes = 0;
+ var totalBytesReceived = 0;
+ var anyMissingTotalBytes = false;
+ var anyDangerous = false;
+ var anyPaused = false;
+ var anyRecentlyCompleted = false;
+ var anyInProgress = false;
+ items.forEach(function(item) {
+ if (item.state == 'in_progress') {
+ anyInProgress = true;
+ if (item.totalBytes) {
+ totalTotalBytes += item.totalBytes;
+ totalBytesReceived += item.bytesReceived;
+ } else {
+ anyMissingTotalBytes = true;
+ }
+ var dangerous = ((item.danger != 'safe') &&
+ (item.danger != 'accepted'));
+ anyDangerous = anyDangerous || dangerous;
+ anyPaused = anyPaused || item.paused;
+ } else if ((item.state == 'complete') && item.endTime && !item.error) {
+ var ended = (new Date(item.endTime)).getTime();
+ var recentlyCompleted = (ended >= popupLastOpened);
+ anyRecentlyCompleted = anyRecentlyCompleted || recentlyCompleted;
+ maybeOpen(item.id);
+ }
+ });
+ var stage = !anyInProgress ? 'n' : (anyMissingTotalBytes ? 'm' :
+ parseInt((totalBytesReceived / totalTotalBytes) * 15));
+ var badge = anyDangerous ? 'd' : (anyPaused ? 'p' :
+ (anyRecentlyCompleted ? 'c' : ''));
+
+ var targetIcon = stage + ' ' + badge;
+ if (sessionStorage.currentIcon != targetIcon) {
+ setBrowserActionIcon(stage, badge);
+ sessionStorage.currentIcon = targetIcon;
+ }
+
+ if (anyInProgress &&
+ (pollProgress.tid < 0)) {
+ pollProgress.start();
+ }
+ });
+}
+pollProgress.tid = -1;
+pollProgress.MS = 200;
+
+pollProgress.start = function() {
+ if (pollProgress.tid < 0) {
+ pollProgress.tid = setTimeout(pollProgress, pollProgress.MS);
+ }
+};
+
+function isNumber(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+}
+
+if (!isNumber(localStorage.popupLastOpened)) {
+ localStorage.popupLastOpened = '' + (new Date()).getTime();
+}
+
+chrome.downloads.onCreated.addListener(function(item) {
+ pollProgress();
+});
+
+pollProgress();
+
+function openWhenComplete(downloadId) {
+ var ids = [];
+ try {
+ ids = JSON.parse(localStorage.openWhenComplete);
+ } catch (e) {
+ localStorage.openWhenComplete = JSON.stringify(ids);
+ }
+ pollProgress.start();
+ if (ids.indexOf(downloadId) >= 0) {
+ return;
+ }
+ ids.push(downloadId);
+ localStorage.openWhenComplete = JSON.stringify(ids);
+}
+
+chrome.runtime.onMessage.addListener(function(request) {
+ if (request == 'poll') {
+ pollProgress.start();
+ }
+ if (request == 'icons') {
+ [16, 19, 38, 128].forEach(function(s) {
+ var canvas = drawIcon(s, 'n', '');
+ chrome.downloads.download({
+ url: canvas.toDataURL('image/png', 1.0),
+ filename: 'icon' + s + '.png',
+ });
+ canvas.parentNode.removeChild(canvas);
+ });
+ }
+ if (isNumber(request.openWhenComplete)) {
+ openWhenComplete(request.openWhenComplete);
+ }
+});
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/icon128.png b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icon128.png
new file mode 100644
index 0000000..ff20437
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icon128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/icon19.png b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icon19.png
new file mode 100644
index 0000000..ad88a21
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icon19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/icon38.png b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icon38.png
new file mode 100644
index 0000000..cd33c09
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icon38.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/icons.html b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icons.html
new file mode 100644
index 0000000..fca4bf2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icons.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Icon Generator</title>
+<script src='icons.js'></script>
+</head>
+<body>
+<button id=download>Generate Manifest Icons</button>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/icons.js b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icons.js
new file mode 100644
index 0000000..d1bc098
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/icons.js
@@ -0,0 +1,12 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+window.onload = function() {
+ var download = document.getElementById('download');
+ download.onclick = function() {
+ chrome.runtime.sendMessage('icons');
+ download.disabled = true;
+ return false;
+ };
+};
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/manifest.json b/chrome/common/extensions/docs/examples/api/downloads/download_manager/manifest.json
new file mode 100644
index 0000000..85f2d0e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/manifest.json
@@ -0,0 +1,14 @@
+{"name": "__MSG_extName__",
+ "version": "0.1",
+ "manifest_version": 2,
+ "description": "__MSG_extDesc__",
+ "icons": {"128": "icon128.png"},
+ "browser_action": {
+ "default_icon": {
+ "19": "icon19.png",
+ "38": "icon38.png"},
+ "default_title": "__MSG_extName__",
+ "default_popup": "popup.html"},
+ "background": {"persistent": false, "scripts": ["background.js"]},
+ "default_locale": "en",
+ "permissions": ["management", "downloads", "downloads.open"]}
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.css b/chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.css
new file mode 100644
index 0000000..3624a86
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.css
@@ -0,0 +1,229 @@
+/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+body {
+ text-align: center;
+}
+
+#outer,
+#empty,
+#open-folder,
+.file-url,
+.time-left,
+.more-left {
+ display: inline-block;
+}
+
+#head {
+ position: fixed;
+ width: 100%;
+ left: 0;
+ top: 0;
+ background: white;
+ border-bottom: 1px solid grey;
+}
+
+#search-zero,
+#items {
+ margin-top: 2em;
+}
+
+#head,
+#empty,
+#searching,
+#search-zero,
+.item,
+.start-time,
+.complete-size,
+.error,
+.file-url-head,
+.removed,
+.open-filename,
+.progress,
+.time-left,
+.url,
+#text-width-probe {
+ white-space: nowrap;
+}
+
+#head,
+.item {
+ text-align: left;
+}
+
+#empty {
+ vertical-align: bottom;
+ height: 2em;
+}
+
+#q {
+ margin-top: 0.5em;
+ margin-left: 3%;
+}
+
+#clear-all,
+#open-folder {
+ float: right;
+}
+
+svg {
+ width: 2em;
+ height: 2em;
+}
+
+svg rect.border {
+ fill-opacity: 0;
+ stroke-width: 6;
+}
+
+#open-folder svg,
+.show-folder svg {
+ stroke: brown;
+ fill: white;
+ stroke-width: 3;
+}
+
+#clear-all svg {
+ fill: white;
+ stroke-width: 3;
+}
+
+#clear-all svg,
+.remove-file svg,
+.erase svg {
+ stroke: black;
+}
+
+.clearfix {
+ clear: both;
+ float: right;
+}
+
+#older,
+#loading-older,
+.item {
+ margin-top: 1em;
+}
+
+.more {
+ position: absolute;
+ border: 1px solid grey;
+ background: white;
+ z-index: 1;
+}
+
+.complete-size,
+.error,
+.removed,
+.open-filename,
+.progress,
+.time-left {
+ margin-right: 0.4em;
+}
+
+.error {
+ color: darkred;
+}
+
+.referrer svg {
+ stroke: blue;
+ fill-opacity: 0;
+ stroke-width: 7;
+}
+
+.removed,
+.open-filename {
+ max-width: 400px;
+ overflow: hidden;
+ display: inline-block;
+}
+
+.remove-file svg {
+ fill-opacity: 0;
+ stroke-width: 5;
+}
+
+.remove-file svg line {
+ stroke-width: 7;
+ stroke: red;
+}
+
+.erase svg ellipse,
+.erase svg line {
+ fill-opacity: 0;
+ stroke-width: 5;
+}
+
+.pause svg {
+ stroke: #333;
+}
+
+.resume svg {
+ stroke: rgb(68, 187, 68);
+ fill: rgb(68, 187, 68);
+}
+
+.cancel svg line {
+ stroke-width: 7;
+}
+
+.cancel svg {
+ stroke: rgb(204, 68, 68);
+}
+
+.meter {
+ height: 0.5em;
+ position: relative;
+ background: grey;
+}
+
+.meter > span {
+ display: block;
+ height: 100%;
+ background-color: rgb(43, 194, 83);
+ background-image: -webkit-gradient(
+ linear, left bottom, left top,
+ color-stop(0, rgb(43,194,83)),
+ color-stop(1, rgb(84,240,84)));
+ position: relative;
+ overflow: hidden;
+}
+
+.meter > span:after {
+ content: '';
+ position: absolute;
+ top: 0; left: 0; bottom: 0; right: 0;
+ background-image: -webkit-gradient(
+ linear, 0 0, 100% 100%,
+ color-stop(.25, rgba(255, 255, 255, .2)),
+ color-stop(.25, transparent),
+ color-stop(.5, transparent),
+ color-stop(.5, rgba(255, 255, 255, .2)),
+ color-stop(.75, rgba(255, 255, 255, .2)),
+ color-stop(.75, transparent),
+ to(transparent));
+ z-index: 1;
+ -webkit-background-size: 50px 50px;
+ background-size: 50px 50px;
+ -webkit-animation: move 2s linear infinite;
+ overflow: hidden;
+}
+
+@-webkit-keyframes move {
+ 0% { background-position: 0 0; }
+ 100% { background-position: 50px 50px; }
+}
+
+.url {
+ color: grey;
+ max-width: 700px;
+ overflow: hidden;
+ display: block;
+}
+
+#text-width-probe {
+ position: absolute;
+ top: -100px;
+ left: 0;
+}
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.html b/chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.html
new file mode 100644
index 0000000..08bda79
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title></title>
+ <script src="popup.js"></script>
+ <link rel="stylesheet" type="text/css" href="popup.css">
+ <link rel="icon" href="icon19.png">
+</head>
+<body>
+<div id="outer">
+ <div id="head">
+ <a id="bad-chrome-version" hidden
+ href="https://www.google.com/intl/en/chrome/browser/beta.html"></a>
+ <span id="empty" hidden></span>
+ <input type="search" id="q" incremental>
+ <a href="#" id="clear-all"><svg viewBox="0 0 100 100"><g>
+ <rect x="5" y="5" rx="20" ry="20" width="90" height="90" class="border" />
+ <ellipse rx="30" ry="8" cx="50" cy="55" />
+ <ellipse rx="20" ry="5" cx="50" cy="80" />
+ <line x1="20" y1="55" x2="30" y2="83" />
+ <line x1="40" y1="60" x2="43" y2="83" />
+ <line x1="60" y1="60" x2="56" y2="83" />
+ <line x1="80" y1="55" x2="69" y2="83" />
+ <polygon points="25,20 35,20 35,40 40,40 30,55 20,40 25,40" />
+ <polygon points="45,20 55,20 55,40 60,40 50,55 40,40 45,40" />
+ <polygon points="65,20 75,20 75,40 80,40 70,55 60,40 65,40" />
+ </g></svg></a>
+ <a href="#" id="open-folder"><svg viewBox="0 0 100 100"><g>
+ <rect x="5" y="5" rx="20" ry="20" width="90" height="90" class="border" />
+ <polygon points="20,20 20,80 60,80 60,30 35,30 35,20" />
+ <polygon points="20,80 60,80 80,45 40,45" />
+ </g></svg></a>
+ <div class="clearfix"> </div>
+ </div>
+ <div id="searching" hidden></div>
+ <div id="search-zero" hidden></div>
+ <div id="items"></div>
+ <a href="#" id="older" hidden></a>
+ <span id="loading-older" hidden></span>
+</div>
+
+<div class="item" hidden>
+ <img class="icon" src="icon38.png">
+ <div class="more" hidden>
+ <span class="more-left">
+ <div class="start-time"></div>
+ <div class="complete-size"></div>
+ </span>
+ <a href="#" class="referrer"><svg viewBox="0 0 100 100"><g>
+ <rect x="5" y="5" rx="20" ry="20" width="90" height="90" class="border" />
+ <ellipse cx="35" cy="50" rx="20" ry="20" />
+ <ellipse cx="60" cy="50" rx="20" ry="20" />
+ </g></svg></a>
+ <a href="#" class="show-folder"><svg viewBox="0 0 100 100"><g>
+ <rect x="5" y="5" rx="20" ry="20" width="90" height="90" class="border" />
+ <polygon points="20,20 20,80 60,80 60,30 35,30 35,20" />
+ <polygon points="20,80 60,80 80,45 40,45" />
+ </g></svg></a>
+ <a href="#" class="remove-file"><svg viewBox="0 0 100 100"><g>
+ <rect x="5" y="5" rx="20" ry="20" width="90" height="90" class="border" />
+ <polygon points="20,45 45,20 75,20 75,75 20,75, 20,45 45,45 45,20" />
+ <line x1="45" y1="45" x2="80" y2="80" />
+ <line x1="80" y1="45" x2="45" y2="80" />
+ </g></svg></a>
+ <a href="#" class="erase"><svg viewBox="0 0 100 100"><g>
+ <rect x="5" y="5" rx="20" ry="20" width="90" height="90" class="border" />
+ <ellipse rx="30" ry="10" cx="50" cy="40" />
+ <ellipse rx="20" ry="10" cx="50" cy="70" />
+ <line x1="20" y1="35" x2="30" y2="75" />
+ <line x1="80" y1="35" x2="65" y2="75" />
+ <line x1="50" y1="50" x2="50" y2="80" />
+ </g></svg></a>
+ </div>
+ <a href="#" class="pause"><svg viewBox="0 0 100 100"><g>
+ <rect x="5" y="5" rx="20" ry="20" width="90" height="90" class="border" />
+ <rect x="25" y="25" rx="20" ry="20" width="15" height="50" />
+ <rect x="55" y="25" rx="20" ry="20" width="15" height="50" />
+ </g></svg></a>
+ <a href="#" class="resume"><svg viewBox="0 0 100 100"><g>
+ <rect x="5" y="5" rx="20" ry="20" width="90" height="90" class="border" />
+ <polygon points="25,25 75,50 25,75">
+ </g></svg></a>
+ <a href="#" class="cancel"><svg viewBox="0 0 100 100"><g>
+ <rect x="5" y="5" rx="20" ry="20" width="90" height="90" class="border" />
+ <line x1="25" y1="25" x2="75" y2="75" />
+ <line x1="75" y1="25" x2="25" y2="75" />
+ </g></svg></a>
+ <span class="file-url">
+ <div class="file-url-head">
+ <span class="removed"></span>
+ <a href="#" class="open-filename"></a>
+ <span class="error"></span>
+ <span class="in-progress">
+ <span class="progress"></span>
+ <span class="time-left"></span>
+ </span>
+ </div>
+ <div class="meter"><span /></div>
+ <a href="#" class="url" download=""></a>
+ </span>
+</div>
+<span id="text-width-probe"></span>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.js b/chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.js
new file mode 100644
index 0000000..68212c9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_manager/popup.js
@@ -0,0 +1,713 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+function pointInElement(p, elem) {
+ return ((p.x >= elem.offsetLeft) &&
+ (p.x <= (elem.offsetLeft + elem.offsetWidth)) &&
+ (p.y >= elem.offsetTop) &&
+ (p.y <= (elem.offsetTop + elem.offsetHeight)));
+};
+
+function setLastOpened() {
+ localStorage.popupLastOpened = (new Date()).getTime();
+ chrome.runtime.sendMessage('poll');
+};
+
+function loadI18nMessages() {
+ function setProperty(selector, prop, msg) {
+ document.querySelector(selector)[prop] = chrome.i18n.getMessage(msg);
+ }
+
+ setProperty('title', 'innerText', 'tabTitle');
+ setProperty('#q', 'placeholder', 'searchPlaceholder');
+ setProperty('#clear-all', 'title', 'clearAllTitle');
+ setProperty('#open-folder', 'title', 'openDownloadsFolderTitle');
+ setProperty('#empty', 'innerText', 'zeroItems');
+ setProperty('#searching', 'innerText', 'searching');
+ setProperty('#search-zero', 'innerText', 'zeroSearchResults');
+ setProperty('#older', 'innerText', 'showOlderDownloads');
+ setProperty('#loading-older', 'innerText', 'loadingOlderDownloads');
+ setProperty('.pause', 'title', 'pauseTitle');
+ setProperty('.resume', 'title', 'resumeTitle');
+ setProperty('.cancel', 'title', 'cancelTitle');
+ setProperty('.show-folder', 'title', 'showInFolderTitle');
+ setProperty('.erase', 'title', 'eraseTitle');
+ setProperty('.url', 'title', 'retryTitle');
+ setProperty('.referrer', 'title', 'referrerTitle');
+ setProperty('.open-filename', 'title', 'openTitle');
+ setProperty('#bad-chrome-version', 'innerText', 'badChromeVersion');
+ setProperty('.remove-file', 'title', 'removeFileTitle');
+
+ document.querySelector('.progress').style.minWidth =
+ getTextWidth(formatBytes(1024 * 1024 * 1023.9) + '/' +
+ formatBytes(1024 * 1024 * 1023.9)) + 'px';
+
+ // This only covers {timeLeft,openWhenComplete}{Finishing,Days}. If
+ // ...Hours/Minutes/Seconds could be longer for any locale, then this should
+ // test them.
+ var max_time_left_width = 0;
+ for (var i = 0; i < 4; ++i) {
+ max_time_left_width = Math.max(max_time_left_width, getTextWidth(
+ formatTimeLeft(0 == (i % 2),
+ (i < 2) ? 0 : ((100 * 24) + 23) * 60 * 60 * 1000)));
+ }
+ document.querySelector('body div.item span.time-left').style.minWidth =
+ max_time_left_width + 'px';
+};
+
+function getTextWidth(s) {
+ var probe = document.getElementById('text-width-probe');
+ probe.innerText = s;
+ return probe.offsetWidth;
+};
+
+function formatDateTime(date) {
+ var now = new Date();
+ var zpad_mins = ':' + (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
+ if (date.getYear() != now.getYear()) {
+ return '' + (1900 + date.getYear());
+ } else if ((date.getMonth() != now.getMonth()) ||
+ (date.getDate() != now.getDate())) {
+ return date.getDate() + ' ' + chrome.i18n.getMessage(
+ 'month' + date.getMonth() + 'abbr');
+ } else if (date.getHours() == 12) {
+ return '12' + zpad_mins + 'pm';
+ } else if (date.getHours() > 12) {
+ return (date.getHours() - 12) + zpad_mins + 'pm';
+ }
+ return date.getHours() + zpad_mins + 'am';
+}
+
+function formatBytes(n) {
+ if (n < 1024) {
+ return n + 'B';
+ }
+ var prefixes = 'KMGTPEZY';
+ var mul = 1024;
+ for (var i = 0; i < prefixes.length; ++i) {
+ if (n < (1024 * mul)) {
+ return (parseInt(n / mul) + '.' + parseInt(10 * ((n / mul) % 1)) +
+ prefixes[i] + 'B');
+ }
+ mul *= 1024;
+ }
+ return '!!!';
+}
+
+function formatTimeLeft(openWhenComplete, ms) {
+ var prefix = openWhenComplete ? 'openWhenComplete' : 'timeLeft';
+ if (ms < 1000) {
+ return chrome.i18n.getMessage(prefix + 'Finishing');
+ }
+ var days = parseInt(ms / (24 * 60 * 60 * 1000));
+ var hours = parseInt(ms / (60 * 60 * 1000)) % 24;
+ if (days) {
+ return chrome.i18n.getMessage(prefix + 'Days', [days, hours]);
+ }
+ var minutes = parseInt(ms / (60 * 1000)) % 60;
+ if (hours) {
+ return chrome.i18n.getMessage(prefix + 'Hours', [hours, minutes]);
+ }
+ var seconds = parseInt(ms / 1000) % 60;
+ if (minutes) {
+ return chrome.i18n.getMessage(prefix + 'Minutes', [minutes, seconds]);
+ }
+ return chrome.i18n.getMessage(prefix + 'Seconds', [seconds]);
+}
+
+function binarySearch(array, target, cmp) {
+ var low = 0, high = array.length - 1, i, comparison;
+ while (low <= high) {
+ i = (low + high) >> 1;
+ comparison = cmp(target, array[i]);
+ if (comparison < 0) {
+ low = i + 1;
+ } else if (comparison > 0) {
+ high = i - 1;
+ } else {
+ return i;
+ }
+ }
+ return i;
+};
+
+function arrayFrom(seq) {
+ return Array.prototype.slice.apply(seq);
+};
+
+function DownloadItem(data) {
+ var item = this;
+ for (var prop in data) {
+ item[prop] = data[prop];
+ }
+ item.startTime = new Date(item.startTime);
+ if (item.canResume == undefined) {
+ DownloadItem.canResumeHack = true;
+ }
+
+ item.div = document.querySelector('body>div.item').cloneNode(true);
+ item.div.id = 'item' + item.id;
+ item.div.item = item;
+
+ var items_div = document.getElementById('items');
+ if ((items_div.childNodes.length == 0) ||
+ (item.startTime.getTime() < items_div.childNodes[
+ items_div.childNodes.length - 1].item.startTime.getTime())) {
+ items_div.appendChild(item.div);
+ } else if (item.startTime.getTime() >
+ items_div.childNodes[0].item.startTime.getTime()) {
+ items_div.insertBefore(item.div, items_div.childNodes[0]);
+ } else {
+ var adjacent_div = items_div.childNodes[
+ binarySearch(arrayFrom(items_div.childNodes),
+ item.startTime.getTime(),
+ function(target, other) {
+ return target - other.item.startTime.getTime();
+ })];
+ var adjacent_item = adjacent_div.item;
+ if (adjacent_item.startTime.getTime() < item.startTime.getTime()) {
+ items_div.insertBefore(item.div, adjacent_div);
+ } else {
+ items_div.insertBefore(item.div, adjacent_div.nextSibling);
+ }
+ }
+
+ item.getElement('referrer').onclick = function() {
+ chrome.tabs.create({url: item.referrer});
+ return false;
+ };
+ item.getElement('open-filename').onclick = function() {
+ item.open();
+ return false;
+ };
+ item.getElement('open-filename').ondragstart = function() {
+ item.drag();
+ return false;
+ };
+ item.getElement('pause').onclick = function() {
+ item.pause();
+ return false;
+ };
+ item.getElement('cancel').onclick = function() {
+ item.cancel();
+ return false;
+ };
+ item.getElement('resume').onclick = function() {
+ item.resume();
+ return false;
+ };
+ item.getElement('show-folder').onclick = function() {
+ item.show();
+ return false;
+ };
+ item.getElement('remove-file').onclick = function() {
+ item.removeFile();
+ return false;
+ };
+ item.getElement('erase').onclick = function() {
+ item.erase();
+ return false;
+ };
+
+ item.more_mousemove = function(evt) {
+ if (item.getElement('more') &&
+ (pointInElement(evt, item.div) ||
+ pointInElement(evt, item.getElement('more')))) {
+ return;
+ }
+ if (item.getElement('more')) {
+ item.getElement('more').hidden = true;
+ }
+ window.removeEventListener('mousemove', item.more_mousemove);
+ };
+ [item.getElement('icon'), item.getElement('more')].concat(
+ item.getElement('more').children).forEach(function(elem) {
+ elem.onmouseover = function() {
+ arrayFrom(items_div.children).forEach(function(other) {
+ if (other.item != item) {
+ other.item.getElement('more').hidden = true;
+ }
+ });
+ item.getElement('more').hidden = false;
+ item.getElement('more').style.top =
+ (item.div.offsetTop + item.div.offsetHeight) + 'px';
+ item.getElement('more').style.left = item.div.offsetLeft + 'px';
+ if (window.innerHeight < (parseInt(item.getElement('more').style.top) +
+ item.getElement('more').offsetHeight)) {
+ item.getElement('more').style.top = (
+ item.div.offsetTop - item.getElement('more').offsetHeight) + 'px';
+ }
+ window.addEventListener('mousemove', item.more_mousemove);
+ };
+ });
+
+ if (item.referrer) {
+ item.getElement('referrer').href = item.referrer;
+ } else {
+ item.getElement('referrer').hidden = true;
+ }
+ item.getElement('url').href = item.url;
+ item.getElement('url').innerText = item.url;
+ item.render();
+}
+DownloadItem.canResumeHack = false;
+
+DownloadItem.prototype.getElement = function(name) {
+ return document.querySelector('#item' + this.id + ' .' + name);
+};
+
+DownloadItem.prototype.render = function() {
+ var item = this;
+ var now = new Date();
+ var in_progress = (item.state == 'in_progress')
+ var openable = (item.state != 'interrupted') && item.exists && !item.deleted;
+
+ item.startTime = new Date(item.startTime);
+ if (DownloadItem.canResumeHack) {
+ item.canResume = in_progress && item.paused;
+ }
+ if (item.filename) {
+ item.basename = item.filename.substring(Math.max(
+ item.filename.lastIndexOf('\\'),
+ item.filename.lastIndexOf('/')) + 1);
+ }
+ if (item.estimatedEndTime) {
+ item.estimatedEndTime = new Date(item.estimatedEndTime);
+ }
+ if (item.endTime) {
+ item.endTime = new Date(item.endTime);
+ }
+
+ if (item.filename && !item.icon_url) {
+ chrome.downloads.getFileIcon(
+ item.id,
+ {'size': 32},
+ function(icon_url) {
+ item.getElement('icon').hidden = !icon_url;
+ if (icon_url) {
+ item.icon_url = icon_url;
+ item.getElement('icon').src = icon_url;
+ }
+ });
+ }
+
+ item.getElement('removed').style.display = openable ? 'none' : 'inline-block';
+ item.getElement('open-filename').style.display = (
+ openable ? 'inline-block' : 'none');
+ item.getElement('in-progress').hidden = !in_progress;
+ item.getElement('pause').style.display = (
+ !in_progress || item.paused) ? 'none' : 'inline-block';
+ item.getElement('resume').style.display = (
+ !in_progress || !item.canResume) ? 'none' : 'inline-block';
+ item.getElement('cancel').style.display = (
+ !in_progress ? 'none' : 'inline-block');
+ item.getElement('remove-file').hidden = (
+ (item.state != 'complete') ||
+ !item.exists ||
+ item.deleted ||
+ !chrome.downloads.removeFile);
+ item.getElement('erase').hidden = in_progress;
+
+ var could_progress = in_progress || item.canResume;
+ item.getElement('progress').style.display = (
+ could_progress ? 'inline-block' : 'none');
+ item.getElement('meter').hidden = !could_progress || !item.totalBytes;
+
+ item.getElement('removed').innerText = item.basename;
+ item.getElement('open-filename').innerText = item.basename;
+
+ if (!item.getElement('error').hidden) {
+ if (item.error) {
+ // TODO(benjhayden) When https://codereview.chromium.org/16924017/ is
+ // released, set minimum_chrome_version and remove the error_N messages.
+ item.getElement('error').innerText = chrome.i18n.getMessage(
+ 'error_' + item.error);
+ if (!item.getElement('error').innerText) {
+ item.getElement('error').innerText = item.error;
+ }
+ } else if (!openable) {
+ item.getElement('error').innerText = chrome.i18n.getMessage(
+ 'errorRemoved');
+ }
+ }
+
+ item.getElement('complete-size').innerText = formatBytes(
+ item.bytesReceived);
+ if (item.totalBytes && (item.state != 'complete')) {
+ item.getElement('progress').innerText = (
+ item.getElement('complete-size').innerText + '/' +
+ formatBytes(item.totalBytes));
+ item.getElement('meter').children[0].style.width = parseInt(
+ 100 * item.bytesReceived / item.totalBytes) + '%';
+ }
+
+ if (in_progress) {
+ if (item.estimatedEndTime && !item.paused) {
+ var openWhenComplete = false;
+ try {
+ openWhenComplete = JSON.parse(localStorage.openWhenComplete).indexOf(
+ item.id) >= 0;
+ } catch (e) {
+ }
+ item.getElement('time-left').innerText = formatTimeLeft(
+ openWhenComplete, item.estimatedEndTime.getTime() - now.getTime());
+ } else {
+ item.getElement('time-left').innerText = String.fromCharCode(160);
+ }
+ }
+
+ if (item.startTime) {
+ item.getElement('start-time').innerText = formatDateTime(
+ item.startTime);
+ }
+
+ this.maybeAccept();
+};
+
+DownloadItem.prototype.onChanged = function(delta) {
+ for (var key in delta) {
+ if (key != 'id') {
+ this[key] = delta[key].current;
+ }
+ }
+ this.render();
+ if (delta.state) {
+ setLastOpened();
+ }
+ if ((this.state == 'in_progress') && !this.paused) {
+ DownloadManager.startPollingProgress();
+ }
+};
+
+DownloadItem.prototype.onErased = function() {
+ window.removeEventListener('mousemove', this.more_mousemove);
+ document.getElementById('items').removeChild(this.div);
+};
+
+DownloadItem.prototype.drag = function() {
+ chrome.downloads.drag(this.id);
+};
+
+DownloadItem.prototype.show = function() {
+ chrome.downloads.show(this.id);
+};
+
+DownloadItem.prototype.open = function() {
+ if (this.state == 'complete') {
+ chrome.downloads.open(this.id);
+ return;
+ }
+ chrome.runtime.sendMessage({openWhenComplete:this.id});
+};
+
+DownloadItem.prototype.removeFile = function() {
+ chrome.downloads.removeFile(this.id);
+ this.deleted = true;
+ this.render();
+};
+
+DownloadItem.prototype.erase = function() {
+ chrome.downloads.erase({id: this.id});
+};
+
+DownloadItem.prototype.pause = function() {
+ chrome.downloads.pause(this.id);
+};
+
+DownloadItem.prototype.resume = function() {
+ chrome.downloads.resume(this.id);
+};
+
+DownloadItem.prototype.cancel = function() {
+ chrome.downloads.cancel(this.id);
+};
+
+DownloadItem.prototype.maybeAccept = function() {
+ // This function is safe to call at any time for any item, and it will always
+ // do the right thing, which is to display the danger prompt only if the item
+ // is in_progress and dangerous, and if the prompt is not already displayed.
+ if ((this.state != 'in_progress') ||
+ (this.danger == 'safe') ||
+ (this.danger == 'accepted') ||
+ DownloadItem.prototype.maybeAccept.accepting_danger) {
+ return;
+ }
+ DownloadItem.prototype.maybeAccept.accepting_danger = true;
+ chrome.downloads.acceptDanger(this.id, function() {
+ DownloadItem.prototype.maybeAccept.accepting_danger = false;
+ arrayFrom(document.getElementById('items').childNodes).forEach(
+ function(item_div) { item_div.item.maybeAccept(); });
+ });
+};
+DownloadItem.prototype.maybeAccept.accepting_danger = false;
+
+var DownloadManager = {};
+
+DownloadManager.showingOlder = false;
+
+DownloadManager.getItem = function(id) {
+ var item_div = document.getElementById('item' + id);
+ return item_div ? item_div.item : null;
+};
+
+DownloadManager.getOrCreate = function(data) {
+ var item = DownloadManager.getItem(data.id);
+ return item ? item : new DownloadItem(data);
+};
+
+DownloadManager.forEachItem = function(cb) {
+ // Calls cb(item, index) in the order that they are displayed, i.e. in order
+ // of decreasing startTime.
+ arrayFrom(document.getElementById('items').childNodes).forEach(
+ function(item_div, index) { cb(item_div.item, index); });
+};
+
+DownloadManager.startPollingProgress = function() {
+ if (DownloadManager.startPollingProgress.tid < 0) {
+ DownloadManager.startPollingProgress.tid = setTimeout(
+ DownloadManager.startPollingProgress.pollProgress,
+ DownloadManager.startPollingProgress.MS);
+ }
+}
+DownloadManager.startPollingProgress.MS = 200;
+DownloadManager.startPollingProgress.tid = -1;
+DownloadManager.startPollingProgress.pollProgress = function() {
+ DownloadManager.startPollingProgress.tid = -1;
+ chrome.downloads.search({state: 'in_progress', paused: false},
+ function(results) {
+ if (!results.length)
+ return;
+ results.forEach(function(result) {
+ var item = DownloadManager.getOrCreate(result);
+ for (var prop in result) {
+ item[prop] = result[prop];
+ }
+ item.render();
+ if ((item.state == 'in_progress') && !item.paused) {
+ DownloadManager.startPollingProgress();
+ }
+ });
+ });
+};
+
+DownloadManager.showNew = function() {
+ var any_items = (document.getElementById('items').childNodes.length > 0);
+ document.getElementById('empty').style.display =
+ any_items ? 'none' : 'inline-block';
+ document.getElementById('clear-all').hidden = !any_items;
+ document.getElementById('open-folder').style.float =
+ any_items ? 'right' : 'none';
+
+ var query_search = document.getElementById('q');
+ query_search.hidden = !any_items;
+
+ if (!any_items) {
+ return;
+ }
+ var old_ms = (new Date()).getTime() - kOldMs;
+ var any_hidden = false;
+ var any_showing = false;
+ // First show up to kShowNewMax items newer than kOldMs. If there aren't any
+ // items newer than kOldMs, then show up to kShowNewMax items of any age. If
+ // there are any hidden items, show the Show Older button.
+ DownloadManager.forEachItem(function(item, index) {
+ item.div.hidden = !DownloadManager.showingOlder && (
+ (item.startTime.getTime() < old_ms) || (index >= kShowNewMax));
+ any_hidden = any_hidden || item.div.hidden;
+ any_showing = any_showing || !item.div.hidden;
+ });
+ if (!any_showing) {
+ any_hidden = false;
+ DownloadManager.forEachItem(function(item, index) {
+ item.div.hidden = !DownloadManager.showingOlder && (index >= kShowNewMax);
+ any_hidden = any_hidden || item.div.hidden;
+ any_showing = any_showing || !item.div.hidden;
+ });
+ }
+ document.getElementById('older').hidden = !any_hidden;
+
+ query_search.focus();
+};
+
+DownloadManager.showOlder = function() {
+ DownloadManager.showingOlder = true;
+ var loading_older_span = document.getElementById('loading-older');
+ document.getElementById('older').hidden = true;
+ loading_older_span.hidden = false;
+ chrome.downloads.search({}, function(results) {
+ results.forEach(function(result) {
+ var item = DownloadManager.getOrCreate(result);
+ item.div.hidden = false;
+ });
+ loading_older_span.hidden = true;
+ });
+};
+
+DownloadManager.onSearch = function() {
+ // split string by space, but ignore space in quotes
+ // http://stackoverflow.com/questions/16261635
+ var query = document.getElementById('q').value.match(/(?:[^\s"]+|"[^"]*")+/g);
+ if (!query) {
+ DownloadManager.showNew();
+ document.getElementById('search-zero').hidden = true;
+ } else {
+ query = query.map(function(term) {
+ // strip quotes
+ return (term.match(/\s/) &&
+ term[0].match(/["']/) &&
+ term[term.length - 1] == term[0]) ?
+ term.substr(1, term.length - 2) : term;
+ });
+ var searching = document.getElementById('searching');
+ searching.hidden = false;
+ chrome.downloads.search({query: query}, function(results) {
+ document.getElementById('older').hidden = true;
+ DownloadManager.forEachItem(function(item) {
+ item.div.hidden = true;
+ });
+ results.forEach(function(result) {
+ DownloadManager.getOrCreate(result).div.hidden = false;
+ });
+ searching.hidden = true;
+ document.getElementById('search-zero').hidden = (results.length != 0);
+ });
+ }
+};
+
+DownloadManager.clearAll = function() {
+ DownloadManager.forEachItem(function(item) {
+ if (!item.div.hidden) {
+ item.erase();
+ // The onErased handler should circle back around to loadItems.
+ }
+ });
+};
+
+var kShowNewMax = 50;
+var kOldMs = 1000 * 60 * 60 * 24 * 7;
+
+// These settings can be tuned by modifying localStorage in dev-tools.
+if ('kShowNewMax' in localStorage) {
+ kShowNewMax = parseInt(localStorage.kShowNewMax);
+}
+if ('kOldMs' in localStorage) {
+ kOldMs = parseInt(localStorage.kOldMs);
+}
+
+DownloadManager.loadItems = function() {
+ // Request up to kShowNewMax + 1, but only display kShowNewMax; the +1 is a
+ // probe to see if there are any older downloads.
+ // TODO(benjhayden) When https://codereview.chromium.org/16924017/ is
+ // released, set minimum_chrome_version and remove this try/catch.
+ try {
+ chrome.downloads.search({
+ orderBy: ['-startTime'],
+ limit: kShowNewMax + 1},
+ function(results) {
+ DownloadManager.loadItems.items = results;
+ DownloadManager.loadItems.onLoaded();
+ });
+ } catch (exc) {
+ chrome.downloads.search({
+ orderBy: '-startTime',
+ limit: kShowNewMax + 1},
+ function(results) {
+ DownloadManager.loadItems.items = results;
+ DownloadManager.loadItems.onLoaded();
+ });
+ }
+};
+DownloadManager.loadItems.items = [];
+DownloadManager.loadItems.window_loaded = false;
+
+DownloadManager.loadItems.onLoaded = function() {
+ if (!DownloadManager.loadItems.window_loaded) {
+ return;
+ }
+ DownloadManager.loadItems.items.forEach(function(item) {
+ DownloadManager.getOrCreate(item);
+ });
+ DownloadManager.loadItems.items = [];
+ DownloadManager.showNew();
+};
+
+DownloadManager.loadItems.onWindowLoaded = function() {
+ DownloadManager.loadItems.window_loaded = true;
+ DownloadManager.loadItems.onLoaded();
+};
+
+// If this extension is installed on a stable-channel chrome, where the
+// downloads API is not available, do not use the downloads API, and link to the
+// beta channel.
+if (chrome.downloads) {
+ // Start searching ASAP, don't wait for onload.
+ DownloadManager.loadItems();
+
+ chrome.downloads.onCreated.addListener(function(item) {
+ DownloadManager.getOrCreate(item);
+ DownloadManager.showNew();
+ DownloadManager.startPollingProgress();
+ });
+
+ chrome.downloads.onChanged.addListener(function(delta) {
+ var item = DownloadManager.getItem(delta.id);
+ if (item) {
+ item.onChanged(delta);
+ }
+ });
+
+ chrome.downloads.onErased.addListener(function(id) {
+ var item = DownloadManager.getItem(id);
+ if (!item) {
+ return;
+ }
+ item.onErased();
+ DownloadManager.loadItems();
+ });
+
+ window.onload = function() {
+ document.body.style.minWidth = (
+ document.getElementById('q').offsetWidth +
+ document.getElementById('clear-all').offsetWidth +
+ document.getElementById('open-folder').offsetWidth) + 'px';
+ setLastOpened();
+ loadI18nMessages();
+ DownloadManager.loadItems.onWindowLoaded();
+ document.getElementById('older').onclick = function() {
+ DownloadManager.showOlder();
+ return false;
+ };
+ document.getElementById('q').onsearch = function() {
+ DownloadManager.onSearch();
+ };
+ document.getElementById('clear-all').onclick = function() {
+ DownloadManager.clearAll();
+ return false;
+ };
+ if (chrome.downloads.showDefaultFolder) {
+ document.getElementById('open-folder').onclick = function() {
+ chrome.downloads.showDefaultFolder();
+ return false;
+ };
+ } else {
+ document.getElementById('open-folder').hidden = true;
+ }
+ };
+} else {
+ // The downloads API is not available.
+ // TODO(benjhayden) Remove this when minimum_chrome_version is set.
+ window.onload = function() {
+ loadI18nMessages();
+ var bad_version = document.getElementById('bad-chrome-version');
+ bad_version.hidden = false;
+ bad_version.onclick = function() {
+ chrome.tabs.create({url: bad_version.href});
+ return false;
+ };
+ document.getElementById('empty').style.display = 'none';
+ document.getElementById('q').style.display = 'none';
+ document.getElementById('open-folder').style.display = 'none';
+ document.getElementById('clear-all').style.display = 'none';
+ };
+}
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_open/_locales/en/messages.json b/chrome/common/extensions/docs/examples/api/downloads/download_open/_locales/en/messages.json
new file mode 100644
index 0000000..5a6df11
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_open/_locales/en/messages.json
@@ -0,0 +1,9 @@
+{"extName": {
+ "message": "Download and Open Context Menu Button",
+ "description": "Extension name"},
+ "extDesc": {
+ "message": "Download and Open Context Menu Button",
+ "description": "Extension description"},
+ "openContextMenuTitle": {
+ "message": "Download and Open",
+ "description": "context menu button text"}}
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_open/background.js b/chrome/common/extensions/docs/examples/api/downloads/download_open/background.js
new file mode 100644
index 0000000..bfc40ec
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_open/background.js
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+function getOpeningIds() {
+ var ids = [];
+ try {
+ ids = JSON.parse(localStorage.openWhenComplete);
+ } catch (e) {
+ localStorage.openWhenComplete = JSON.stringify(ids);
+ }
+ return ids;
+}
+
+function setOpeningIds(ids) {
+ localStorage.openWhenComplete = JSON.stringify(ids);
+}
+
+chrome.downloads.onChanged.addListener(function(delta) {
+ if (!delta.state ||
+ (delta.state.current != 'complete')) {
+ return;
+ }
+ var ids = getOpeningIds();
+ if (ids.indexOf(delta.id) < 0) {
+ return;
+ }
+ chrome.downloads.open(delta.id);
+ ids.splice(ids.indexOf(delta.id), 1);
+ setOpeningIds(ids);
+});
+
+chrome.contextMenus.onClicked.addListener(function(info, tab) {
+ chrome.downloads.download({url: info.linkUrl}, function(downloadId) {
+ var ids = getOpeningIds();
+ if (ids.indexOf(downloadId) >= 0) {
+ return;
+ }
+ ids.push(downloadId);
+ setOpeningIds(ids);
+ });
+});
+
+chrome.contextMenus.create({
+ id: 'open',
+ title: chrome.i18n.getMessage('openContextMenuTitle'),
+ contexts: ['link'],
+});
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_open/icon128.png b/chrome/common/extensions/docs/examples/api/downloads/download_open/icon128.png
new file mode 100644
index 0000000..ff20437
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_open/icon128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_open/icon16.png b/chrome/common/extensions/docs/examples/api/downloads/download_open/icon16.png
new file mode 100644
index 0000000..ad88a21
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_open/icon16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_open/manifest.json b/chrome/common/extensions/docs/examples/api/downloads/download_open/manifest.json
new file mode 100644
index 0000000..98a49ab
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_open/manifest.json
@@ -0,0 +1,8 @@
+{"name": "__MSG_extName__",
+ "version": "0.1",
+ "manifest_version": 2,
+ "description": "__MSG_extDesc__",
+ "icons": {"16": "icon16.png", "128": "icon128.png"},
+ "background": {"persistent": false, "scripts": ["background.js"]},
+ "default_locale": "en",
+ "permissions": ["contextMenus", "downloads", "downloads.open"]}