From cdadb0db4d5eaaa2d7bd62a5551d191eeb598ef4 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Tue, 13 Aug 2013 18:28:13 +0200 Subject: [PATCH 1/5] Proof of concept using chrome.streamsPrivate API This method captures all application/pdf streams, loads the viewer and passes the stream to the PDF.js viewer. This commit shows a proof of concept using the chrome.streamsPrivate API. Advantages of new method: - Access to the response body of the original request, thus fewer network requests. - PDFs from non-GET requests (e.g. POST) are now supported. - FTP files are also supported. Possible improvements: - Use declared content scripts instead of dynamic chrome.tabs.executeScript. This allows the extension to render the viewer in frames when the extension is disallowed to run executeScript for the top URL. - Use chrome.declarativeWebRequest instead of webRequest, and replace background page with event page (don't forget to profile the difference & will the background/event page still work as intended?). --- extensions/chromium/manifest.json | 12 ++- extensions/chromium/pdfHandler-v2.js | 105 +++++++++++++++++++++++++++ extensions/chromium/pdfHandler.html | 1 + web/chromecom.js | 46 ++++++++++++ web/viewer.js | 36 ++++++++- 5 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 extensions/chromium/pdfHandler-v2.js create mode 100644 web/chromecom.js diff --git a/extensions/chromium/manifest.json b/extensions/chromium/manifest.json index b709b780d..f19986246 100644 --- a/extensions/chromium/manifest.json +++ b/extensions/chromium/manifest.json @@ -12,12 +12,22 @@ "webRequest", "webRequestBlocking", "", "tabs", - "webNavigation" + "webNavigation", + "streamsPrivate" ], + /* FOR demo & debugging purposes only! This key is required to get access to the streams API. + * This key forces the extension ID to be gbkeegbaiigmenfmjfclcdgdpimamgkj (= Chrome Office Viewer) + * This comment has been added to prevent it from being uploaded to the Chrome Web Store. + * Remove it when the PDF.js extensionID is whitelisted for the streamsPrivate API. + */ + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4zyYTii0VTKI7W2U6fDeAvs3YCVZeAt7C62IC64IDCMHvWy7SKMpOPjfg5v1PgYkFm+fGsCsVLN8NaF7fzYMVtjLc5bqhqPAi56Qidrqh1HxPAAYhwFQd5BVGhZmh1fySHXFPE8VI2tIHwRrASOtx67jbSEk4nBAcJz6n+eGq8QIDAQAB", "content_scripts": [{ "matches": ["file://*/*"], "js": ["nothing.js"] }], + "mime_types": [ + "application/pdf" + ], "background": { "page": "pdfHandler.html" }, diff --git a/extensions/chromium/pdfHandler-v2.js b/extensions/chromium/pdfHandler-v2.js new file mode 100644 index 000000000..155eb0e53 --- /dev/null +++ b/extensions/chromium/pdfHandler-v2.js @@ -0,0 +1,105 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* +Copyright 2013 Mozilla Foundation + +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. +*/ +/* globals chrome, URL, getViewerURL */ + +'use strict'; + +// Hash map of "": "" +var urlToStream = {}; + +// Note: Execution of this script stops when the streamsPrivate API is +// not available, because an error will be thrown. Don't bother with +// catching and handling the error, because it is a great way to see +// when the streamsPrivate API is unavailable. +chrome.streamsPrivate.onExecuteMimeTypeHandler.addListener(handleStream); + +chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { + if (message && message.action === 'getPDFStream') { + var pdfUrl = message.data; + var streamUrl = urlToStream[pdfUrl]; + // The stream can be used only once. + delete urlToStream[pdfUrl]; + sendResponse({ + streamUrl: streamUrl + }); + } +}); + +/** + * Callback for when we receive a stream + * + * @param mimeType {string} The mime type of the incoming stream + * @param pdfUrl {string} The full URL to the file + * @param streamUrl {string} The url pointing to the open stream + * @param tabId {number} The ID of the tab in which the stream has been opened + * (undefined before Chrome 27, http://crbug.com/225605) + */ +function handleStream(mimeType, pdfUrl, streamUrl, tabId) { + console.log('Intercepted ' + mimeType + ' in tab ' + tabId + ' with URL ' + + pdfUrl + '\nAvailable as: ' + streamUrl); + urlToStream[pdfUrl] = streamUrl; +} + +/** + * Callback for when a navigation error has occurred. + * This event is triggered when the chrome.streamsPrivate API has intercepted + * the PDF stream. This method detects such streams, finds the frame where + * the request was made, and loads the viewer in that frame. + * + * @param details {object} + * @param details.tabId {number} The ID of the tab + * @param details.url {string} The URL being navigated when the error occurred. + * @param details.frameId {number} 0 indicates the navigation happens in the tab + * content window; a positive value indicates + * navigation in a subframe. + * @param details.error {string} + */ +function webNavigationOnErrorOccurred(details) { + var tabId = details.tabId; + var frameId = details.frameId; + var pdfUrl = details.url; + + if (details.error === 'net::ERR_ABORTED') { + if (!urlToStream[pdfUrl]) { + console.log('No saved PDF stream found for ' + pdfUrl); + return; + } + var viewerUrl = getViewerURL(pdfUrl); + + if (frameId === 0) { // Main frame + console.log('Going to render PDF Viewer in main frame for ' + pdfUrl); + chrome.tabs.update(tabId, { + url: viewerUrl + }); + } else { + console.log('Going to render PDF Viewer in sub frame for ' + pdfUrl); + // Non-standard Chrome API. chrome.tabs.executeScriptInFrame and docs + // is available at https://github.com/Rob--W/chrome-api + chrome.tabs.executeScriptInFrame(tabId, { + frameId: frameId, + code: 'location.href = ' + JSON.stringify(viewerUrl) + ';' + }, function(result) { + if (!result) { // Did the tab disappear? Is the frame inaccessible? + console.warn('Frame not found, viewer not rendered in tab ' + tabId); + } + }); + } + } +} + +chrome.webNavigation.onErrorOccurred.addListener(webNavigationOnErrorOccurred); diff --git a/extensions/chromium/pdfHandler.html b/extensions/chromium/pdfHandler.html index dcb70cb31..df35754e0 100644 --- a/extensions/chromium/pdfHandler.html +++ b/extensions/chromium/pdfHandler.html @@ -17,3 +17,4 @@ limitations under the License. + diff --git a/web/chromecom.js b/web/chromecom.js new file mode 100644 index 000000000..6e1a12b59 --- /dev/null +++ b/web/chromecom.js @@ -0,0 +1,46 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright 2013 Mozilla Foundation + * + * 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. + */ + +/* globals chrome */ +'use strict'; + +var ChromeCom = (function ChromeComClosure() { + return { + /** + * Creates an event that the extension is listening for and will + * asynchronously respond by calling the callback. + * @param {String} action The action to trigger. + * @param {String} data Optional data to send. + * @param {Function} callback Optional response callback that will be called + * with one data argument. When the request cannot be handled, the callback + * is immediately invoked with no arguments. + */ + request: function(action, data, callback) { + var message = { + action: action, + data: data + }; + if (!chrome.runtime) { + console.error('chrome.runtime is undefined.'); + if (callback) callback(); + } else if (callback) { + chrome.runtime.sendMessage(message, callback); + } else { + chrome.runtime.sendMessage(message); + } + } + }; +})(); diff --git a/web/viewer.js b/web/viewer.js index 516e75ba3..37e45a40e 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -79,6 +79,10 @@ var mozL10n = document.mozL10n || document.webL10n; //#include firefoxcom.js //#endif +//#if CHROME +//#include chromecom.js +//#endif + var cache = new Cache(CACHE_SIZE); var currentPageNumber = 1; @@ -1770,9 +1774,39 @@ document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) { //return; //#endif -//#if !B2G +//#if !B2G && !CHROME PDFView.open(file, 0); //#endif + +//#if CHROME +//ChromeCom.request('getPDFStream', file, function(response) { +// if (response) { +// // We will only get a response when the streamsPrivate API is available. +// +// var isFTPFile = /^ftp:/i.test(file); +// var streamUrl = response.streamUrl; +// if (streamUrl) { +// console.log('Found data stream for ' + file); +// // The blob stream can be used only once, so disable range requests. +// PDFJS.disableRange = true; +// PDFView.open(streamUrl, 0); +// PDFView.setTitleUsingUrl(file); +// return; +// } +// if (isFTPFile) { +// // Stream not found, and it's loaded from FTP. Reload the page, because +// // it is not possible to get resources over ftp using XMLHttpRequest. +// // NOTE: This will not lead to an infinite redirect loop, because +// // if the file exists, then the streamsPrivate API will capture the +// // stream and send back the response. If the stream does not exist, then +// // a "Webpage not available" error will be shown (not the PDF Viewer). +// location.replace(file); +// return; +// } +// } +// PDFView.open(file, 0); +//}); +//#endif }, true); function updateViewarea() { From 32313b9149b0849731ce4501551cd68cdea91c96 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Thu, 5 Dec 2013 11:16:14 +0100 Subject: [PATCH 2/5] Insert dummy content script at every location. This is needed for propagating the extension's permissions to the extension's iframe, in the rare event that the PDF is loaded in a sub frame, and the extension does not have access to the top frame. For instance, when a http:-PDF file is embedded in a local file, while "Allow access to local URLs" is disabled. Note: Propagating permissions by inserting content scripts is an undocumented feature (http://crbug.com/302548). Whenever it breaks, the issue (cross-domain permissions for XHR) can be solved by using a content script that gets the blob using the XMLHttpRequest API, followed by `postMessage` (via transferables) to efficiently pass the arraybuffer back to the PDF Viewer. --- extensions/chromium/manifest.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/chromium/manifest.json b/extensions/chromium/manifest.json index f19986246..48802ed97 100644 --- a/extensions/chromium/manifest.json +++ b/extensions/chromium/manifest.json @@ -22,7 +22,12 @@ */ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4zyYTii0VTKI7W2U6fDeAvs3YCVZeAt7C62IC64IDCMHvWy7SKMpOPjfg5v1PgYkFm+fGsCsVLN8NaF7fzYMVtjLc5bqhqPAi56Qidrqh1HxPAAYhwFQd5BVGhZmh1fySHXFPE8VI2tIHwRrASOtx67jbSEk4nBAcJz6n+eGq8QIDAQAB", "content_scripts": [{ - "matches": ["file://*/*"], + "matches": [ + "http://*/*", + "https://*/*", + "ftp://*/*", + "file://*/*" + ], "js": ["nothing.js"] }], "mime_types": [ From 94ba01c8aab9dd0135cc7ee7fa322f7ad06aece8 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Thu, 5 Dec 2013 19:01:17 +0100 Subject: [PATCH 3/5] Use tab-specific stream storage Also: - Use webNavigation.getAllFrames to find out whether the navigation has already started. This is (at least) needed for top-level navigation to a stream. The webNavigation.onErrorOccurred event has become obsolete, and has been removed. --- extensions/chromium/pdfHandler-v2.js | 147 ++++++++++++++++++++------- 1 file changed, 112 insertions(+), 35 deletions(-) diff --git a/extensions/chromium/pdfHandler-v2.js b/extensions/chromium/pdfHandler-v2.js index 155eb0e53..d70b42fdd 100644 --- a/extensions/chromium/pdfHandler-v2.js +++ b/extensions/chromium/pdfHandler-v2.js @@ -19,7 +19,11 @@ limitations under the License. 'use strict'; -// Hash map of "": "" +// +// Stream URL storage manager +// + +// Hash map of "": { "": ["", ...], ... } var urlToStream = {}; // Note: Execution of this script stops when the streamsPrivate API is @@ -28,18 +32,56 @@ var urlToStream = {}; // when the streamsPrivate API is unavailable. chrome.streamsPrivate.onExecuteMimeTypeHandler.addListener(handleStream); +// Chrome before 27 does not support tabIds on stream events. +var streamSupportsTabId = true; +// "tabId" used for Chrome before 27. +var STREAM_NO_TABID = 0; + +function hasStream(tabId, pdfUrl) { + var streams = urlToStream[streamSupportsTabId ? tabId : STREAM_NO_TABID]; + return streams && streams[pdfUrl] && streams[pdfUrl].length > 0; +} + +/** +* Get stream URL for a given tabId and PDF url. The retrieved stream URL +* will be removed from the list. +* @return {string|undefined} The blob:-URL +*/ +function getStream(tabId, pdfUrl) { + if (!streamSupportsTabId) tabId = STREAM_NO_TABID; + if (hasStream(tabId, pdfUrl)) { + var streamUrl = urlToStream[tabId][pdfUrl].shift(); + if (urlToStream[tabId][pdfUrl].length === 0) { + delete urlToStream[tabId][pdfUrl]; + if (Object.keys(urlToStream[tabId]).length === 0) { + delete urlToStream[tabId]; + } + } + return streamUrl; + } +} + +function setStream(tabId, pdfUrl, streamUrl) { + tabId = tabId || STREAM_NO_TABID; + if (!urlToStream[tabId]) urlToStream[tabId] = {}; + if (!urlToStream[tabId][pdfUrl]) urlToStream[tabId][pdfUrl] = []; + urlToStream[tabId][pdfUrl].push(streamUrl); +} + chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { if (message && message.action === 'getPDFStream') { var pdfUrl = message.data; - var streamUrl = urlToStream[pdfUrl]; - // The stream can be used only once. - delete urlToStream[pdfUrl]; + var streamUrl = getStream(sender.tab.id, pdfUrl); sendResponse({ streamUrl: streamUrl }); } }); +// +// PDF detection and activation of PDF viewer. +// + /** * Callback for when we receive a stream * @@ -52,12 +94,51 @@ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { function handleStream(mimeType, pdfUrl, streamUrl, tabId) { console.log('Intercepted ' + mimeType + ' in tab ' + tabId + ' with URL ' + pdfUrl + '\nAvailable as: ' + streamUrl); - urlToStream[pdfUrl] = streamUrl; + streamSupportsTabId = typeof tabId === 'number'; + + setStream(tabId, pdfUrl, streamUrl); + + if (!tabId) { // Chrome doesn't set the tabId before v27 + // PDF.js targets Chrome 28+ because of fatal bugs in incognito mode + // for older versions of Chrome. So, don't bother implementing a fallback. + // For those who are interested, either loop through all tabs, or use the + // webNavigation.onBeforeNavigate event to map pdfUrls to tab + frame IDs. + return; + } + + // Check if the frame has already been rendered. + chrome.webNavigation.getAllFrames({ + tabId: tabId + }, function(details) { + if (details) { + details = details.filter(function(frame) { + return frame.url === pdfUrl; + }); + if (details.length > 0) { + if (details.length !== 1) { + // (Rare case) Multiple frames with same URL. + // TODO(rob): Find a better way to handle this case. + console.warn('More than one frame found for tabId ' + tabId + + ' with URL ' + pdfUrl + '. Using first frame.'); + } + details = details[0]; + details = { + tabId: tabId, + frameId: details.frameId, + url: details.url + }; + handleWebNavigation(details); + } else { + console.warn('No webNavigation frames found for tabId ' + tabId); + } + } else { + console.warn('Unable to get frame information for tabId ' + tabId); + } + }); } /** - * Callback for when a navigation error has occurred. - * This event is triggered when the chrome.streamsPrivate API has intercepted + * This method is called when the chrome.streamsPrivate API has intercepted * the PDF stream. This method detects such streams, finds the frame where * the request was made, and loads the viewer in that frame. * @@ -67,39 +148,35 @@ function handleStream(mimeType, pdfUrl, streamUrl, tabId) { * @param details.frameId {number} 0 indicates the navigation happens in the tab * content window; a positive value indicates * navigation in a subframe. - * @param details.error {string} */ -function webNavigationOnErrorOccurred(details) { +function handleWebNavigation(details) { var tabId = details.tabId; var frameId = details.frameId; var pdfUrl = details.url; - if (details.error === 'net::ERR_ABORTED') { - if (!urlToStream[pdfUrl]) { - console.log('No saved PDF stream found for ' + pdfUrl); - return; - } - var viewerUrl = getViewerURL(pdfUrl); + if (!hasStream(tabId, pdfUrl)) { + console.log('No PDF stream found in tab ' + tabId + ' for ' + pdfUrl); + return; + } - if (frameId === 0) { // Main frame - console.log('Going to render PDF Viewer in main frame for ' + pdfUrl); - chrome.tabs.update(tabId, { - url: viewerUrl - }); - } else { - console.log('Going to render PDF Viewer in sub frame for ' + pdfUrl); - // Non-standard Chrome API. chrome.tabs.executeScriptInFrame and docs - // is available at https://github.com/Rob--W/chrome-api - chrome.tabs.executeScriptInFrame(tabId, { - frameId: frameId, - code: 'location.href = ' + JSON.stringify(viewerUrl) + ';' - }, function(result) { - if (!result) { // Did the tab disappear? Is the frame inaccessible? - console.warn('Frame not found, viewer not rendered in tab ' + tabId); - } - }); - } + var viewerUrl = getViewerURL(pdfUrl); + + if (frameId === 0) { // Main frame + console.log('Going to render PDF Viewer in main frame for ' + pdfUrl); + chrome.tabs.update(tabId, { + url: viewerUrl + }); + } else { + console.log('Going to render PDF Viewer in sub frame for ' + pdfUrl); + // Non-standard Chrome API. chrome.tabs.executeScriptInFrame and docs + // is available at https://github.com/Rob--W/chrome-api + chrome.tabs.executeScriptInFrame(tabId, { + frameId: frameId, + code: 'location.href = ' + JSON.stringify(viewerUrl) + ';' + }, function(result) { + if (!result) { // Did the tab disappear? Is the frame inaccessible? + console.warn('Frame not found, viewer not rendered in tab ' + tabId); + } + }); } } - -chrome.webNavigation.onErrorOccurred.addListener(webNavigationOnErrorOccurred); From af31ace940337f10ab2516df2db5ac951e4a55cc Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Wed, 21 Aug 2013 18:37:22 +0200 Subject: [PATCH 4/5] Work-around for http://crbug.com/276898 When a new incognito session is started, the onExecuteMimeTypeHandler event is often not dispatched in time. Instead, it's triggered in the non-incognito profile. This commit offers a work-around that allows new incognito instances to view PDF files. --- extensions/chromium/manifest.json | 1 + extensions/chromium/pdfHandler-v2.js | 57 +++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/extensions/chromium/manifest.json b/extensions/chromium/manifest.json index 48802ed97..c029d12a4 100644 --- a/extensions/chromium/manifest.json +++ b/extensions/chromium/manifest.json @@ -13,6 +13,7 @@ "", "tabs", "webNavigation", + "storage", "streamsPrivate" ], /* FOR demo & debugging purposes only! This key is required to get access to the streams API. diff --git a/extensions/chromium/pdfHandler-v2.js b/extensions/chromium/pdfHandler-v2.js index d70b42fdd..91b90bfb9 100644 --- a/extensions/chromium/pdfHandler-v2.js +++ b/extensions/chromium/pdfHandler-v2.js @@ -68,6 +68,56 @@ function setStream(tabId, pdfUrl, streamUrl) { urlToStream[tabId][pdfUrl].push(streamUrl); } +// http://crbug.com/276898 - the onExecuteMimeTypeHandler event is sometimes +// dispatched in the wrong incognito profile. To work around the bug, transfer +// the stream information from the incognito session when the bug is detected. +function transferStreamToIncognitoProfile(tabId, pdfUrl) { + if (chrome.extension.inIncognitoContext) { + console.log('Already within incognito profile. Aborted stream transfer.'); + return; + } + var streamUrl = getStream(tabId, pdfUrl); + console.log('Attempting to transfer stream info to a different profile...'); + var itemId = 'streamInfo:' + window.performance.now(); + var items = {}; + items[itemId] = { + tabId: tabId, + pdfUrl: pdfUrl, + streamUrl: streamUrl + }; + // The key will be removed whenever an incognito session is started, + // or when an incognito session is active. + chrome.storage.local.set(items, function() { + chrome.extension.isAllowedIncognitoAccess(function(isAllowedAccess) { + if (!isAllowedAccess) { + // If incognito is disabled, forget about the stream. + console.warn('Incognito is disabled, unexpected unknown stream.'); + chrome.storage.local.remove(items); + } + }); + }); +} +if (chrome.extension.inIncognitoContext) { + var importStream = function(itemId, streamInfo) { + if (itemId.lastIndexOf('streamInfo:', 0) !== 0) return; + console.log('Importing stream info from non-incognito profile', streamInfo); + handleStream('', streamInfo.pdfUrl, streamInfo.streamUrl, streamInfo.tabId); + chrome.storage.local.remove(itemId); + }; + var handleStorageItems = function(items) { + Object.keys(items).forEach(function(itemId) { + var item = items[itemId]; + if (item.oldValue && !item.newValue) return; // storage remove event + if (item.newValue) item = item.newValue; // storage setter event + importStream(itemId, item); + }); + }; + // Parse information that was set before the event pages were ready. + chrome.storage.local.get(null, handleStorageItems); + chrome.storage.onChanged.addListener(handleStorageItems); +} +// End of work-around for crbug 276898 + chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { if (message && message.action === 'getPDFStream') { var pdfUrl = message.data; @@ -117,7 +167,8 @@ function handleStream(mimeType, pdfUrl, streamUrl, tabId) { if (details.length > 0) { if (details.length !== 1) { // (Rare case) Multiple frames with same URL. - // TODO(rob): Find a better way to handle this case. + // TODO(rob): Find a better way to handle this case + // (e.g. open in new tab). console.warn('More than one frame found for tabId ' + tabId + ' with URL ' + pdfUrl + '. Using first frame.'); } @@ -133,6 +184,10 @@ function handleStream(mimeType, pdfUrl, streamUrl, tabId) { } } else { console.warn('Unable to get frame information for tabId ' + tabId); + // This branch may occur when a new incognito session is launched. + // The event is dispatched in the non-incognito session while it should + // be dispatched in the incognito session. See http://crbug.com/276898 + transferStreamToIncognitoProfile(tabId, pdfUrl); } }); } From b754f9d878e51784fb0c6e2889a45ff7d6b42b0c Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Wed, 15 Jan 2014 22:58:55 +0100 Subject: [PATCH 5/5] Make streamsPrivate optional; remove manifest key Use streamsPrivate API when available. When the API is not available, the extension will still work on on http/https/file URLs, but not for POST requests or FTP. As of writing, the Chromium project has still not whitelisted the PDF Viewer extension in the Chrome Web Store. (extension ID oemmndcbldboiebfnladdacbdfmadadm) Request to whitelist PDF.js in Chromium: https://code.google.com/p/chromium/issues/detail?id=326949 Opera 19 has whitelisted the PDF Viewer extension from https://addons.opera.com/extensions/details/pdf-viewer/ (extension ID encfpfilknmenlmjemepncnlbbjlabkc) (https://github.com/Rob--W/pdf.js/issues/1#issuecomment-32357302) If you want to test the streamsPrivate feature in Chrome, edit the build/extensions/manifest.json and add the "key" again (see this commit for the value of this "key" field). --- extensions/chromium/manifest.json | 6 ------ extensions/chromium/pdfHandler-v2.js | 20 ++++++++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/extensions/chromium/manifest.json b/extensions/chromium/manifest.json index c029d12a4..cd760480b 100644 --- a/extensions/chromium/manifest.json +++ b/extensions/chromium/manifest.json @@ -16,12 +16,6 @@ "storage", "streamsPrivate" ], - /* FOR demo & debugging purposes only! This key is required to get access to the streams API. - * This key forces the extension ID to be gbkeegbaiigmenfmjfclcdgdpimamgkj (= Chrome Office Viewer) - * This comment has been added to prevent it from being uploaded to the Chrome Web Store. - * Remove it when the PDF.js extensionID is whitelisted for the streamsPrivate API. - */ - "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4zyYTii0VTKI7W2U6fDeAvs3YCVZeAt7C62IC64IDCMHvWy7SKMpOPjfg5v1PgYkFm+fGsCsVLN8NaF7fzYMVtjLc5bqhqPAi56Qidrqh1HxPAAYhwFQd5BVGhZmh1fySHXFPE8VI2tIHwRrASOtx67jbSEk4nBAcJz6n+eGq8QIDAQAB", "content_scripts": [{ "matches": [ "http://*/*", diff --git a/extensions/chromium/pdfHandler-v2.js b/extensions/chromium/pdfHandler-v2.js index 91b90bfb9..92c621134 100644 --- a/extensions/chromium/pdfHandler-v2.js +++ b/extensions/chromium/pdfHandler-v2.js @@ -17,8 +17,22 @@ limitations under the License. */ /* globals chrome, URL, getViewerURL */ +(function() { 'use strict'; +if (!chrome.streamsPrivate) { + // Aww, PDF.js is still not whitelisted... See http://crbug.com/326949 + console.warn('streamsPrivate not available, PDF from FTP or POST ' + + 'requests will not be displayed using this extension! ' + + 'See http://crbug.com/326949'); + chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { + if (message && message.action === 'getPDFStream') { + sendResponse(); + } + }); + return; +} + // // Stream URL storage manager // @@ -26,10 +40,6 @@ limitations under the License. // Hash map of "": { "": ["", ...], ... } var urlToStream = {}; -// Note: Execution of this script stops when the streamsPrivate API is -// not available, because an error will be thrown. Don't bother with -// catching and handling the error, because it is a great way to see -// when the streamsPrivate API is unavailable. chrome.streamsPrivate.onExecuteMimeTypeHandler.addListener(handleStream); // Chrome before 27 does not support tabIds on stream events. @@ -235,3 +245,5 @@ function handleWebNavigation(details) { }); } } + +})();