@ -19,7 +19,11 @@ limitations under the License.
'use strict' ;
'use strict' ;
// Hash map of "<pdf url>": "<stream url>"
//
// Stream URL storage manager
//
// Hash map of "<tab id>": { "<pdf url>": ["<stream url>", ...], ... }
var urlToStream = { } ;
var urlToStream = { } ;
// Note: Execution of this script stops when the streamsPrivate API is
// Note: Execution of this script stops when the streamsPrivate API is
@ -28,18 +32,56 @@ var urlToStream = {};
// when the streamsPrivate API is unavailable.
// when the streamsPrivate API is unavailable.
chrome . streamsPrivate . onExecuteMimeTypeHandler . addListener ( handleStream ) ;
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 ) {
chrome . runtime . onMessage . addListener ( function ( message , sender , sendResponse ) {
if ( message && message . action === 'getPDFStream' ) {
if ( message && message . action === 'getPDFStream' ) {
var pdfUrl = message . data ;
var pdfUrl = message . data ;
var streamUrl = urlToStream [ pdfUrl ] ;
var streamUrl = getStream ( sender . tab . id , pdfUrl ) ;
// The stream can be used only once.
delete urlToStream [ pdfUrl ] ;
sendResponse ( {
sendResponse ( {
streamUrl : streamUrl
streamUrl : streamUrl
} ) ;
} ) ;
}
}
} ) ;
} ) ;
//
// PDF detection and activation of PDF viewer.
//
/ * *
/ * *
* Callback for when we receive a stream
* 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 ) {
function handleStream ( mimeType , pdfUrl , streamUrl , tabId ) {
console . log ( 'Intercepted ' + mimeType + ' in tab ' + tabId + ' with URL ' +
console . log ( 'Intercepted ' + mimeType + ' in tab ' + tabId + ' with URL ' +
pdfUrl + '\nAvailable as: ' + streamUrl ) ;
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 method is called when the chrome . streamsPrivate API has intercepted
* This event is triggered when the chrome . streamsPrivate API has intercepted
* the PDF stream . This method detects such streams , finds the frame where
* the PDF stream . This method detects such streams , finds the frame where
* the request was made , and loads the viewer in that frame .
* 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
* @ param details . frameId { number } 0 indicates the navigation happens in the tab
* content window ; a positive value indicates
* content window ; a positive value indicates
* navigation in a subframe .
* navigation in a subframe .
* @ param details . error { string }
* /
* /
function webNavigationOnErrorOccurred ( details ) {
function handleWebNavigation ( details ) {
var tabId = details . tabId ;
var tabId = details . tabId ;
var frameId = details . frameId ;
var frameId = details . frameId ;
var pdfUrl = details . url ;
var pdfUrl = details . url ;
if ( details . error === 'net::ERR_ABORTED' ) {
if ( ! hasStream ( tabId , pdfUrl ) ) {
if ( ! urlToStream [ pdfUrl ] ) {
console . log ( 'No PDF stream found in tab ' + tabId + ' for ' + pdfUrl ) ;
console . log ( 'No saved PDF stream found for ' + pdfUrl ) ;
return ;
return ;
}
}
var viewerUrl = getViewerURL ( pdfUrl ) ;
if ( frameId === 0 ) { // Main frame
var viewerUrl = getViewerURL ( pdfUrl ) ;
console . log ( 'Going to render PDF Viewer in main frame for ' + pdfUrl ) ;
chrome . tabs . update ( tabId , {
if ( frameId === 0 ) { // Main frame
url : viewerUrl
console . log ( 'Going to render PDF Viewer in main frame for ' + pdfUrl ) ;
} ) ;
chrome . tabs . update ( tabId , {
} else {
url : viewerUrl
console . log ( 'Going to render PDF Viewer in sub frame for ' + pdfUrl ) ;
} ) ;
// Non-standard Chrome API. chrome.tabs.executeScriptInFrame and docs
} else {
// is available at https://github.com/Rob--W/chrome-api
console . log ( 'Going to render PDF Viewer in sub frame for ' + pdfUrl ) ;
chrome . tabs . executeScriptInFrame ( tabId , {
// Non-standard Chrome API. chrome.tabs.executeScriptInFrame and docs
frameId : frameId ,
// is available at https://github.com/Rob--W/chrome-api
code : 'location.href = ' + JSON . stringify ( viewerUrl ) + ';'
chrome . tabs . executeScriptInFrame ( tabId , {
} , function ( result ) {
frameId : frameId ,
if ( ! result ) { // Did the tab disappear? Is the frame inaccessible?
code : 'location.href = ' + JSON . stringify ( viewerUrl ) + ';'
console . warn ( 'Frame not found, viewer not rendered in tab ' + tabId ) ;
} , 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 ) ;