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?).
This commit is contained in:
Rob Wu 2013-08-13 18:28:13 +02:00
parent ab4f27b272
commit cdadb0db4d
5 changed files with 198 additions and 2 deletions

View File

@ -12,12 +12,22 @@
"webRequest", "webRequestBlocking",
"<all_urls>",
"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"
},

View File

@ -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 "<pdf url>": "<stream url>"
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);

View File

@ -17,3 +17,4 @@ limitations under the License.
<script src="chrome.tabs.executeScriptInFrame.js"></script>
<script src="pdfHandler.js"></script>
<script src="extension-router.js"></script>
<script src="pdfHandler-v2.js"></script>

46
web/chromecom.js Normal file
View File

@ -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);
}
}
};
})();

View File

@ -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() {