First commit

This commit is contained in:
Pierre Hubert
2016-11-19 12:08:12 +01:00
commit 990540b2b9
4706 changed files with 931207 additions and 0 deletions

View File

@ -0,0 +1 @@
content/

View File

@ -0,0 +1,283 @@
/**
* (c) 2013 Rob Wu <gwnRob@gmail.com>
* Released under the MIT license
* https://github.com/Rob--W/chrome-api/chrome.tabs.executeScriptInFrame
*
* Implements the chrome.tabs.executeScriptInFrame API.
* This API is similar to the chrome.tabs.executeScript method, except
* that it also recognizes the "frameId" property.
* This frameId can be obtained through the webNavigation or webRequest API.
*
* When an error occurs, chrome.runtime.lastError is set.
*
* Required permissions:
* webRequest
* webRequestBlocking
* Host permissions for the tab
*
* In addition, the following field must also be set in manifest.json:
* "web_accessible_resources": ["getFrameId"]
*/
/* globals chrome, console */
(function() {
/* jshint browser:true, maxlen:100 */
'use strict';
chrome.tabs.executeScriptInFrame = executeScript;
// This URL is used to communicate the frameId. The resource is never
// visited, so it should be a non-existent location. Do not use *, ", '
// or line breaks in the file name.
var URL_WHAT_IS_MY_FRAME_ID = chrome.extension.getURL('getFrameId');
// The callback will be called within ... ms:
// Don't set a too low value.
var MAXIMUM_RESPONSE_TIME_MS = 1000;
// Callbacks are stored here until they're invoked.
// Key = dummyUrl, value = callback function
var callbacks = {};
chrome.webRequest.onBeforeRequest.addListener(function showFrameId(details) {
// Positive integer frameId >= 0
// Since an image is used as a data transport, we add 1 to get a
// non-zero width.
var frameId = details.frameId + 1;
// Assume that the frameId fits in three bytes - which is a very
// reasonable assumption.
var width = String.fromCharCode(frameId & 0xFF, (frameId >> 8) & 0xFF);
// When frameId > 0xFFFF, use the height to convey the additional
// information. Again, add 1 to make sure that the height is non-zero.
var height = String.fromCharCode((frameId >> 16) + 1, 0);
// Convert data to base64 to avoid loss of bytes
var image = 'data:image/gif;base64,' + btoa(
// 4749 4638 3961 (GIF header)
'GIF89a' +
// Logical Screen Width (LSB)
width +
// Logical Screen Height (LSB)
height +
// "No Global Color Table follows"
'\x00' +
// Background color
'\xff' +
// No aspect information is given
'\x00' +
// (image descriptor)
// Image Separator
'\x2c' +
// Image Position (Left & Top)
'\x00\x00\x00\x00' +
// Image Width (LSB)
width +
// Image Height (LSB)
height +
// Local Color Table is not present
'\x00' +
// (End of image descriptor)
// Image data
'\x02\x02\x44\x01\x00' +
// GIF trailer
'\x3b'
);
return {redirectUrl: image};
}, {
urls: [URL_WHAT_IS_MY_FRAME_ID + '*'],
types: ['image']
}, ['blocking']);
chrome.runtime.onMessage.addListener(function(message, sender,
sendResponse) {
if (message && message.executeScriptCallback) {
var callback = callbacks[message.identifier];
if (callback) {
if (message.hello) {
clearTimeout(callback.timer);
return;
}
delete callbacks[message.identifier];
// Result within an array to be consistent with the
// chrome.tabs.executeScript API.
callback([message.evalResult]);
} else {
console.warn('Callback not found for response in tab ' +
sender.tab.id);
}
}
});
/**
* Execute content script in a specific frame.
*
* @param tabId {integer} required
* @param details.frameId {integer} required
* @param details.code {string} Code or file is required (not both)
* @param details.file {string} Code or file is required (not both)
* @param details.runAt {optional string} One of "document_start",
* "document_end", "document_idle"
* @param callback {optional function(optional result array)} When an error
* occurs, result
* is not set.
*/
function executeScript(tabId, details, callback) {
console.assert(typeof details === 'object',
'details must be an object (argument 0)');
var frameId = details.frameId;
console.assert(typeof tabId === 'number',
'details.tabId must be a number');
console.assert(typeof frameId === 'number',
'details.frameId must be a number');
var sourceType = ('code' in details ? 'code' : 'file');
console.assert(sourceType in details, 'No source code or file specified');
var sourceValue = details[sourceType];
console.assert(typeof sourceValue === 'string',
'details.' + sourceType + ' must be a string');
var runAt = details.runAt;
if (!callback) {
callback = function() {/* no-op*/};
}
console.assert(typeof callback === 'function',
'callback must be a function');
if (frameId === 0) {
// No need for heavy lifting if we want to inject the script
// in the main frame
var injectDetails = {
allFrames: false,
runAt: runAt
};
injectDetails[sourceType] = sourceValue;
chrome.tabs.executeScript(tabId, injectDetails, callback);
return;
}
var identifier = Math.random().toString(36);
if (sourceType === 'code') {
executeScriptInFrame();
} else { // sourceType === 'file'
(function() {
var x = new XMLHttpRequest();
x.open('GET', chrome.extension.getURL(sourceValue), true);
x.onload = function() {
sourceValue = x.responseText;
executeScriptInFrame();
};
x.onerror = function executeScriptResourceFetchError() {
var message = 'Failed to load file: "' + sourceValue + '".';
console.error('executeScript: ' + message);
chrome.runtime.lastError = chrome.extension.lastError =
{ message: message };
try {
callback();
} finally {
chrome.runtime.lastError = chrome.extension.lastError = undefined;
}
};
x.send();
})();
}
function executeScriptInFrame() {
callbacks[identifier] = callback;
chrome.tabs.executeScript(tabId, {
code: '(' + DETECT_FRAME + ')(' +
'window,' +
JSON.stringify(identifier) + ',' +
frameId + ',' +
JSON.stringify(sourceValue) + ')',
allFrames: true,
runAt: 'document_start'
}, function(results) {
if (results) {
callback.timer = setTimeout(executeScriptTimedOut,
MAXIMUM_RESPONSE_TIME_MS);
} else {
// Failed :(
delete callbacks[identifier];
callback();
}
});
}
function executeScriptTimedOut() {
var callback = callbacks[identifier];
if (!callback) {
return;
}
delete callbacks[identifier];
var message = 'Failed to execute script: Frame ' + frameId +
' not found in tab ' + tabId;
console.error('executeScript: ' + message);
chrome.runtime.lastError = chrome.extension.lastError =
{ message: message };
try {
callback();
} finally {
chrome.runtime.lastError = chrome.extension.lastError = undefined;
}
}
}
/**
* Code executed as a content script.
*/
var DETECT_FRAME = '' + function checkFrame(window, identifier, frameId,
code) {
var i;
if ('__executeScript_frameId__' in window) {
evalAsContentScript();
} else {
// Do NOT use new Image() because of http://crbug.com/245296
// in Chrome 27-29
i = window.document.createElement('img');
i.onload = function() {
window.__executeScript_frameId__ = (this.naturalWidth - 1) +
(this.naturalHeight - 1 << 16);
evalAsContentScript();
};
// Trigger webRequest event to get frameId
// (append extra characters to bust the cache)
i.src = 'URL_WHAT_IS_MY_FRAME_ID?' +
Math.random().toString(36).slice(-6);
}
for (i = 0 ; i < window.frames.length; ++i) {
try {
var frame = window.frames[i];
var scheme = frame.location.protocol;
if (scheme !== 'https:' && scheme !== 'http:' && scheme !== 'file:') {
checkFrame(frame, identifier, frameId, code);
}
} catch (e) {
// blocked by same origin policy, so it's not a javascript:/about:blank
// URL. chrome.tabs.executeScript will run the script for the frame.
}
}
function evalAsContentScript() {
if (window.__executeScript_frameId__ !== frameId) {
return;
}
// Send an early message to make sure that any blocking code
// in the evaluated code does not cause the time-out in the background
// page to be triggered
chrome.runtime.sendMessage({
executeScriptCallback: true,
hello: true,
identifier: identifier
});
var result = null;
try {
// jshint evil:true
result = window.eval(code);
} finally {
chrome.runtime.sendMessage({
executeScriptCallback: true,
evalResult: result,
identifier: identifier
});
}
}
}.toString().replace('URL_WHAT_IS_MY_FRAME_ID', URL_WHAT_IS_MY_FRAME_ID);
})();

View File

@ -0,0 +1,132 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
Copyright 2014 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, CSS */
'use strict';
var VIEWER_URL = chrome.extension.getURL('content/web/viewer.html');
function getViewerURL(pdf_url) {
return VIEWER_URL + '?file=' + encodeURIComponent(pdf_url);
}
// (un)prefixed property names
var createShadowRoot, shadowRoot;
if (typeof Element.prototype.createShadowRoot !== 'undefined') {
// Chrome 35+
createShadowRoot = 'createShadowRoot';
shadowRoot = 'shadowRoot';
} else if (typeof Element.prototype.webkitCreateShadowRoot !== 'undefined') {
// Chrome 25 - 34
createShadowRoot = 'webkitCreateShadowRoot';
shadowRoot = 'webkitShadowRoot';
try {
document.createElement('embed').webkitCreateShadowRoot();
} catch (e) {
// Only supported since Chrome 33.
createShadowRoot = shadowRoot = '';
}
}
// Only observe the document if we can make use of Shadow DOM.
if (createShadowRoot) {
if (CSS.supports('animation', '0s')) {
document.addEventListener('animationstart', onAnimationStart, true);
} else {
document.addEventListener('webkitAnimationStart', onAnimationStart, true);
}
}
function onAnimationStart(event) {
if (event.animationName === 'pdfjs-detected-object-or-embed') {
watchObjectOrEmbed(event.target);
}
}
// Called for every <object> or <embed> element in the page.
// It does not trigger any Mutation observers, but it may modify the
// shadow DOM rooted under the given element.
// Calling this function multiple times for the same element is safe, i.e.
// it has no side effects.
function watchObjectOrEmbed(elem) {
var mimeType = elem.type;
if (mimeType && 'application/pdf' !== mimeType.toLowerCase()) {
return;
}
// <embed src> <object data>
var srcAttribute = 'src' in elem ? 'src' : 'data';
var path = elem[srcAttribute];
if (!mimeType && !/\.pdf($|[?#])/i.test(path)) {
return;
}
if (elem[shadowRoot]) {
// If the element already has a shadow root, assume that we've already
// seen this element.
return;
}
elem[createShadowRoot]();
function updateViewerFrame() {
var path = elem[srcAttribute];
if (!path) {
elem[shadowRoot].textContent = '';
} else {
elem[shadowRoot].innerHTML =
// Set display: inline-block; to the host element (<embed>/<object>) to
// ensure that the dimensions defined on the host element are applied to
// the iframe (http://crbug.com/358648).
// The styles are declared in the shadow DOM to allow page authors to
// override these styles (e.g. .style.display = 'none';).
'<style>\n' +
// Chrome 35+
':host { display: inline-block; }\n' +
// Chrome 33 and 34 (not 35+ because of http://crbug.com/351248)
'*:not(style):not(iframe) { display: inline-block; }\n' +
'iframe { width: 100%; height: 100%; border: 0; }\n' +
'</style>' +
'<iframe allowfullscreen></iframe>';
elem[shadowRoot].lastChild.src = getEmbeddedViewerURL(path);
}
}
updateViewerFrame();
// Watch for page-initiated changes of the src/data attribute.
var srcObserver = new MutationObserver(updateViewerFrame);
srcObserver.observe(elem, {
attributes: true,
childList: false,
characterData: false,
attributeFilter: [srcAttribute]
});
}
// Get the viewer URL, provided that the path is a valid URL.
function getEmbeddedViewerURL(path) {
var fragment = /^([^#]*)(#.*)?$/.exec(path);
path = fragment[1];
fragment = fragment[2] || '';
// Resolve relative path to document.
var a = document.createElement('a');
a.href = document.baseURI;
a.href = path;
path = a.href;
return getViewerURL(path) + fragment;
}

View File

@ -0,0 +1,13 @@
/**
* Detect creation of <embed> and <object> tags.
*/
@-webkit-keyframes pdfjs-detected-object-or-embed { from {} }
@keyframes pdfjs-detected-object-or-embed { from {} }
object, embed {
-webkit-animation-delay: 0s !important;
-webkit-animation-name: pdfjs-detected-object-or-embed !important;
-webkit-animation-play-state: running !important;
animation-delay: 0s !important;
animation-name: pdfjs-detected-object-or-embed !important;
animation-play-state: running !important;
}

View File

@ -0,0 +1,95 @@
/* -*- 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 */
'use strict';
(function ExtensionRouterClosure() {
var VIEWER_URL = chrome.extension.getURL('content/web/viewer.html');
var CRX_BASE_URL = chrome.extension.getURL('/');
var schemes = [
'http',
'https',
'ftp',
'file',
'chrome-extension',
// Chromium OS
'filesystem',
// Chromium OS, shorthand for filesystem:<origin>/external/
'drive'
];
/**
* @param {string} url The URL prefixed with chrome-extension://.../
* @return {string|undefined} The percent-encoded URL of the (PDF) file.
*/
function parseExtensionURL(url) {
url = url.substring(CRX_BASE_URL.length);
// Find the (url-encoded) colon and verify that the scheme is whitelisted.
var schemeIndex = url.search(/:|%3A/i);
if (schemeIndex === -1) {
return;
}
var scheme = url.slice(0, schemeIndex).toLowerCase();
if (schemes.indexOf(scheme) >= 0) {
url = url.split('#')[0];
if (url.charAt(schemeIndex) === ':') {
url = encodeURIComponent(url);
}
return url;
}
}
// TODO(rob): Use declarativeWebRequest once declared URL-encoding is
// supported, see http://crbug.com/273589
// (or rewrite the query string parser in viewer.js to get it to
// recognize the non-URL-encoded PDF URL.)
chrome.webRequest.onBeforeRequest.addListener(function(details) {
// This listener converts chrome-extension://.../http://...pdf to
// chrome-extension://.../content/web/viewer.html?file=http%3A%2F%2F...pdf
var url = parseExtensionURL(details.url);
if (url) {
url = VIEWER_URL + '?file=' + url;
var i = details.url.indexOf('#');
if (i > 0) {
url += details.url.slice(i);
}
console.log('Redirecting ' + details.url + ' to ' + url);
return { redirectUrl: url };
}
}, {
types: ['main_frame', 'sub_frame'],
urls: schemes.map(function(scheme) {
// Format: "chrome-extension://[EXTENSIONID]/<scheme>*"
return CRX_BASE_URL + scheme + '*';
})
}, ['blocking']);
// When session restore is used, viewer pages may be loaded before the
// webRequest event listener is attached (= page not found).
// Reload these tabs.
chrome.tabs.query({
url: CRX_BASE_URL + '*:*'
}, function(tabsFromLastSession) {
for (var i = 0; i < tabsFromLastSession.length; ++i) {
chrome.tabs.reload(tabsFromLastSession[i].id);
}
});
console.log('Set up extension URL router.');
})();

View File

@ -0,0 +1,128 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
Copyright 2014 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 Features = {
featureDetectLastUA: '',
// Whether ftp: in XMLHttpRequest is allowed
extensionSupportsFTP: false,
// Whether redirectUrl at onHeadersReceived is supported.
webRequestRedirectUrl: false,
};
chrome.storage.local.get(Features, function(features) {
Features = features;
if (features.featureDetectLastUA === navigator.userAgent) {
// Browser not upgraded, so the features did probably not change.
return;
}
// In case of a downgrade, the features must be tested again.
var lastVersion = /Chrome\/\d+\.0\.(\d+)/.exec(features.featureDetectLastUA);
lastVersion = lastVersion ? parseInt(lastVersion[1], 10) : 0;
var newVersion = /Chrome\/\d+\.0\.(\d+)/.exec(navigator.userAgent);
var isDowngrade = newVersion && parseInt(newVersion[1], 10) < lastVersion;
var inconclusiveTestCount = 0;
if (isDowngrade || !features.extensionSupportsFTP) {
features.extensionSupportsFTP = featureTestFTP();
}
if (isDowngrade || !features.webRequestRedirectUrl) {
++inconclusiveTestCount;
// Relatively expensive (and asynchronous) test:
featureTestRedirectOnHeadersReceived(function(result) {
// result = 'yes', 'no' or 'maybe'.
if (result !== 'maybe') {
--inconclusiveTestCount;
}
features.webRequestRedirectUrl = result === 'yes';
checkTestCompletion();
});
}
checkTestCompletion();
function checkTestCompletion() {
// Only stamp the feature detection results when all tests have finished.
if (inconclusiveTestCount === 0) {
Features.featureDetectLastUA = navigator.userAgent;
}
chrome.storage.local.set(Features);
}
});
// Tests whether the extension can perform a FTP request.
// Feature is supported since Chromium 35.0.1888.0 (r256810).
function featureTestFTP() {
var x = new XMLHttpRequest();
// The URL does not need to exist, as long as the scheme is ftp:.
x.open('GET', 'ftp://ftp.mozilla.org/');
try {
x.send();
// Previous call did not throw error, so the feature is supported!
// Immediately abort the request so that the network is not hit at all.
x.abort();
return true;
} catch (e) {
return false;
}
}
// Tests whether redirectUrl at the onHeadersReceived stage is functional.
// Feature is supported since Chromium 35.0.1911.0 (r259546).
function featureTestRedirectOnHeadersReceived(callback) {
// The following URL is really going to be accessed via the network.
// It is the only way to feature-detect this feature, because the
// onHeadersReceived event is only triggered for http(s) requests.
var url = 'http://example.com/?feature-detect-' + chrome.runtime.id;
function onHeadersReceived(details) {
// If supported, the request is redirected.
// If not supported, the return value is ignored.
return {
redirectUrl: chrome.runtime.getURL('/manifest.json')
};
}
chrome.webRequest.onHeadersReceived.addListener(onHeadersReceived, {
types: ['xmlhttprequest'],
urls: [url]
}, ['blocking']);
var x = new XMLHttpRequest();
x.open('get', url);
x.onloadend = function() {
chrome.webRequest.onHeadersReceived.removeListener(onHeadersReceived);
if (!x.responseText) {
// Network error? Anyway, can't tell with certainty whether the feature
// is supported.
callback('maybe');
} else if (/^\s*\{/.test(x.responseText)) {
// If the response starts with "{", assume that the redirection to the
// manifest file succeeded, so the feature is supported.
callback('yes');
} else {
// Did not get the content of manifest.json, so the redirect seems not to
// be followed. The feature is not supported.
callback('no');
}
};
x.send();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
3rdparty/pdf.js/extensions/chromium/icon16.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

BIN
3rdparty/pdf.js/extensions/chromium/icon19.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

BIN
3rdparty/pdf.js/extensions/chromium/icon38.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
3rdparty/pdf.js/extensions/chromium/icon48.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,74 @@
{
"manifest_version": 2,
"name": "PDF Viewer",
"version": "PDFJSSCRIPT_VERSION",
"description": "Uses HTML5 to display PDF files directly in the browser.",
"icons": {
"128": "icon128.png",
"48": "icon48.png",
"16": "icon16.png"
},
"permissions": [
"fileBrowserHandler",
"webRequest", "webRequestBlocking",
"<all_urls>",
"tabs",
"webNavigation",
"storage",
"streamsPrivate"
],
"content_scripts": [{
"matches": [
"http://*/*",
"https://*/*",
"ftp://*/*",
"file://*/*"
],
"run_at": "document_start",
"all_frames": true,
"css": ["contentstyle.css"],
"js": ["contentscript.js"]
}],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"file_browser_handlers": [{
"id": "open-as-pdf",
"default_title": "Open with PDF Viewer",
"file_filters": [
"filesystem:*.pdf"
]
}],
"mime_types": [
"application/pdf"
],
"storage": {
"managed_schema": "preferences_schema.json"
},
"options_ui": {
"page": "options/options.html",
"chrome_style": true
},
"options_page": "options/options.html",
"background": {
"page": "pdfHandler.html"
},
"page_action": {
"default_icon": {
"19": "icon19.png",
"38": "icon38.png"
},
"default_title": "Show PDF URL",
"default_popup": "pageActionPopup.html"
},
"incognito": "split",
"web_accessible_resources": [
"getFrameId",
"content/web/viewer.html",
"http:/*",
"https:/*",
"ftp:/*",
"file:/*",
"chrome-extension:/*",
"filesystem:/*",
"drive:*"
]
}

View File

@ -0,0 +1,85 @@
<!doctype html>
<!--
Copyright 2015 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.
-->
<html>
<head>
<meta charset="utf-8">
<title>PDF.js viewer options</title>
<style>
/* TODO: Remove as much custom CSS as possible - crbug.com/446511 */
body {
min-width: 400px; /* a page at the settings page is at least 400px wide */
margin: 14px 17px; /* already added by default in Chrome 40.0.2212.0 */
}
.settings-row {
margin: 0.65em 0;
}
</style>
</head>
<body>
<div id="settings-boxes"></div>
<button id="reset-button">Restore default settings</button>
<template id="checkbox-template">
<!-- Chromium's style: //src/extensions/renderer/resources/extension.css -->
<div class="checkbox">
<label>
<input type="checkbox">
<span></span>
</label>
</div>
</template>
<template id="defaultZoomValue-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="auto" selected="selected">Automatic Zoom</option>
<option value="page-actual">Actual Size</option>
<option value="page-fit">Fit Page</option>
<option value="page-width">Full Width</option>
<option value="custom" class="custom-zoom" hidden></option>
<option value="50">50%</option>
<option value="75">75%</option>
<option value="100">100%</option>
<option value="125">125%</option>
<option value="150">150%</option>
<option value="200">200%</option>
<option value="300">300%</option>
<option value="400">400%</option>
</select>
</label>
</div>
</template>
<template id="sidebarViewOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="0">Do not show sidebar</option>
<option value="1">Show thumbnails in sidebar</option>
<option value="2">Show document outline in sidebar</option>
<option value="3">Show attachments in sidebar</option>
</select>
</label>
</div>
</template>
<script src="options.js"></script>
</body>
</html>

View File

@ -0,0 +1,194 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
Copyright 2015 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, Promise */
'use strict';
Promise.all([
new Promise(function getManagedPrefs(resolve) {
// Get preferences as set by the system administrator.
chrome.storage.managed.get(null, function(prefs) {
// Managed storage may be disabled, e.g. in Opera.
resolve(prefs || {});
});
}),
new Promise(function getUserPrefs(resolve) {
chrome.storage.local.get(null, function(prefs) {
resolve(prefs || {});
});
}),
new Promise(function getStorageSchema(resolve) {
// Get the storage schema - a dictionary of preferences.
var x = new XMLHttpRequest();
var schema_location = chrome.runtime.getManifest().storage.managed_schema;
x.open('get', chrome.runtime.getURL(schema_location));
x.onload = function() {
resolve(x.response.properties);
};
x.responseType = 'json';
x.send();
})
]).then(function(values) {
var managedPrefs = values[0];
var userPrefs = values[1];
var schema = values[2];
function getPrefValue(prefName) {
if (prefName in userPrefs) {
return userPrefs[prefName];
} else if (prefName in managedPrefs) {
return managedPrefs[prefName];
}
return schema[prefName].default;
}
var prefNames = Object.keys(schema);
var renderPreferenceFunctions = {};
// Render options
prefNames.forEach(function(prefName) {
var prefSchema = schema[prefName];
if (!prefSchema.title) {
// Don't show preferences if the title is missing.
return;
}
// A DOM element with a method renderPreference.
var renderPreference;
if (prefSchema.type === 'boolean') {
// Most prefs are booleans, render them in a generic way.
renderPreference = renderBooleanPref(prefSchema.title,
prefSchema.description,
prefName);
} else if (prefName === 'defaultZoomValue') {
renderPreference = renderDefaultZoomValue(prefSchema.title);
} else if (prefName === 'sidebarViewOnLoad') {
renderPreference = renderSidebarViewOnLoad(prefSchema.title);
} else {
// Should NEVER be reached. Only happens if a new type of preference is
// added to the storage manifest.
console.error('Don\'t know how to handle ' + prefName + '!');
return;
}
renderPreference(getPrefValue(prefName));
renderPreferenceFunctions[prefName] = renderPreference;
});
// Names of preferences that are displayed in the UI.
var renderedPrefNames = Object.keys(renderPreferenceFunctions);
// Reset button to restore default settings.
document.getElementById('reset-button').onclick = function() {
userPrefs = {};
chrome.storage.local.remove(prefNames, function() {
renderedPrefNames.forEach(function(prefName) {
renderPreferenceFunctions[prefName](getPrefValue(prefName));
});
});
};
// Automatically update the UI when the preferences were changed elsewhere.
chrome.storage.onChanged.addListener(function(changes, areaName) {
var prefs = areaName === 'local' ? userPrefs :
areaName === 'managed' ? managedPrefs : null;
if (prefs) {
renderedPrefNames.forEach(function(prefName) {
var prefChanges = changes[prefName];
if (prefChanges) {
if ('newValue' in prefChanges) {
userPrefs[prefName] = prefChanges.newValue;
} else {
// Otherwise the pref was deleted
delete userPrefs[prefName];
}
renderPreferenceFunctions[prefName](getPrefValue(prefName));
}
});
}
});
}).then(null, console.error.bind(console));
function importTemplate(id) {
return document.importNode(document.getElementById(id).content, true);
}
// Helpers to create UI elements that display the preference, and return a
// function which updates the UI with the preference.
function renderBooleanPref(shortDescription, description, prefName) {
var wrapper = importTemplate('checkbox-template');
wrapper.title = description;
var checkbox = wrapper.querySelector('input[type="checkbox"]');
checkbox.onchange = function() {
var pref = {};
pref[prefName] = this.checked;
chrome.storage.local.set(pref);
};
wrapper.querySelector('span').textContent = shortDescription;
document.getElementById('settings-boxes').appendChild(wrapper);
function renderPreference(value) {
checkbox.checked = value;
}
return renderPreference;
}
function renderDefaultZoomValue(shortDescription) {
var wrapper = importTemplate('defaultZoomValue-template');
var select = wrapper.querySelector('select');
select.onchange = function() {
chrome.storage.local.set({
defaultZoomValue: this.value
});
};
wrapper.querySelector('span').textContent = shortDescription;
document.getElementById('settings-boxes').appendChild(wrapper);
function renderPreference(value) {
value = value || 'auto';
select.value = value;
var customOption = select.querySelector('option.custom-zoom');
if (select.selectedIndex === -1 && value) {
// Custom zoom percentage, e.g. set via managed preferences.
// [zoom] or [zoom],[left],[top]
customOption.text = value.indexOf(',') > 0 ? value : value + '%';
customOption.value = value;
customOption.hidden = false;
customOption.selected = true;
} else {
customOption.hidden = true;
}
}
return renderPreference;
}
function renderSidebarViewOnLoad(shortDescription) {
var wrapper = importTemplate('sidebarViewOnLoad-template');
var select = wrapper.querySelector('select');
select.onchange = function() {
chrome.storage.local.set({
sidebarViewOnLoad: parseInt(this.value)
});
};
wrapper.querySelector('span').textContent = shortDescription;
document.getElementById('settings-boxes').appendChild(wrapper);
function renderPreference(value) {
select.value = value;
}
return renderPreference;
}

View File

@ -0,0 +1,48 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
Copyright 2014 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';
(function PageActionClosure() {
/**
* @param {number} tabId - ID of tab where the page action will be shown.
* @param {string} url - URL to be displayed in page action.
*/
function showPageAction(tabId, displayUrl) {
// rewriteUrlClosure in viewer.js ensures that the URL looks like
// chrome-extension://[extensionid]/http://example.com/file.pdf
var url = /^chrome-extension:\/\/[a-p]{32}\/([^#]+)/.exec(displayUrl);
if (url) {
url = url[1];
chrome.pageAction.setPopup({
tabId: tabId,
popup: '/pageAction/popup.html?file=' + encodeURIComponent(url)
});
chrome.pageAction.show(tabId);
} else {
console.log('Unable to get PDF url from ' + displayUrl);
}
}
chrome.runtime.onMessage.addListener(function(message, sender) {
if (message === 'showPageAction' && sender.tab) {
showPageAction(sender.tab.id, sender.tab.url);
}
});
})();

View File

@ -0,0 +1,44 @@
<!doctype html>
<!--
Copyright 2012 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.
-->
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
html {
/* maximum width of popup as defined in Chromium's source code as kMaxWidth
//src/chrome/browser/ui/views/extensions/extension_popup.cc
//src/chrome/browser/ui/gtk/extensions/extension_popup_gtk.cc
*/
width: 800px;
/* in case Chromium decides to lower the value of kMaxWidth */
max-width: 100%;
margin: 0;
padding: 0;
}
body {
box-sizing: border-box;
margin: 0;
padding: 5px;
width: 100%;
}
</style>
</head>
<body contentEditable="plaintext-only" spellcheck="false">
<script src="popup.js"></script>
</body>
</html>

View File

@ -0,0 +1,25 @@
/* Copyright 2012 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.
*/
'use strict';
var url = location.search.match(/[&?]file=([^&]+)/i);
if (url) {
url = decodeURIComponent(url[1]);
document.body.textContent = url;
// Set cursor to end of the content-editable section.
window.getSelection().selectAllChildren(document.body);
window.getSelection().collapseToEnd();
}

View File

@ -0,0 +1,287 @@
/* -*- 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, Features */
(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
//
// Hash map of "<tab id>": { "<pdf url>": ["<stream url>", ...], ... }
var urlToStream = {};
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 {object} An object with property url (= blob:-URL) and
* property contentLength (= expected size)
*/
function getStream(tabId, pdfUrl) {
if (!streamSupportsTabId) {
tabId = STREAM_NO_TABID;
}
if (hasStream(tabId, pdfUrl)) {
var streamInfo = 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 streamInfo;
}
}
function setStream(tabId, pdfUrl, streamUrl, expectedSize) {
tabId = tabId || STREAM_NO_TABID;
if (!urlToStream[tabId]) {
urlToStream[tabId] = {};
}
if (!urlToStream[tabId][pdfUrl]) {
urlToStream[tabId][pdfUrl] = [];
}
urlToStream[tabId][pdfUrl].push({
streamUrl: streamUrl,
contentLength: expectedSize
});
}
// 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 streamInfo = getStream(tabId, pdfUrl);
if (!streamInfo) {
return;
}
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: streamInfo.streamUrl,
contentLength: streamInfo.contentLength
};
// 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, streamInfo.contentLength);
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;
var streamInfo = getStream(sender.tab.id, pdfUrl) || {};
sendResponse({
streamUrl: streamInfo.streamUrl,
contentLength: streamInfo.contentLength,
extensionSupportsFTP: Features.extensionSupportsFTP
});
}
});
//
// PDF detection and activation of PDF viewer.
//
/**
* 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)
* @param expectedSize {number} The expected content length of the stream.
* (added in Chrome 29, http://crbug.com/230346)
*/
function handleStream(mimeType, pdfUrl, streamUrl, tabId, expectedSize) {
if (typeof mimeType === 'object') {
// API change: argument list -> object, see crbug.com/345882
// documentation: chrome/common/extensions/api/streams_private.idl
var streamInfo = mimeType;
mimeType = streamInfo.mimeType;
pdfUrl = streamInfo.originalUrl;
streamUrl = streamInfo.streamUrl;
tabId = streamInfo.tabId;
expectedSize = streamInfo.expectedContentSize;
}
console.log('Intercepted ' + mimeType + ' in tab ' + tabId + ' with URL ' +
pdfUrl + '\nAvailable as: ' + streamUrl);
streamSupportsTabId = typeof tabId === 'number';
setStream(tabId, pdfUrl, streamUrl, expectedSize);
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
// (e.g. open in new tab).
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);
// 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);
}
});
}
/**
* 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.
*
* @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.
*/
function handleWebNavigation(details) {
var tabId = details.tabId;
var frameId = details.frameId;
var pdfUrl = details.url;
if (!hasStream(tabId, pdfUrl)) {
console.log('No PDF stream found in tab ' + tabId + ' 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);
}
});
}
}
})();

View File

@ -0,0 +1,95 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
Copyright 2014 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, getViewerURL */
(function() {
'use strict';
if (!chrome.fileBrowserHandler) {
// Not on Chromium OS, bail out
return;
}
chrome.fileBrowserHandler.onExecute.addListener(onExecuteFileBrowserHandler);
/**
* Invoked when "Open with PDF Viewer" is chosen in the File browser.
*
* @param {String} id File browser action ID as specified in
* manifest.json
* @param {Object} details Object of type FileHandlerExecuteEventDetails
*/
function onExecuteFileBrowserHandler(id, details) {
if (id !== 'open-as-pdf') {
return;
}
var fileEntries = details.entries;
// "tab_id" is the currently documented format, but it is inconsistent with
// the other Chrome APIs that use "tabId" (http://crbug.com/179767)
var tabId = details.tab_id || details.tabId;
if (tabId > 0) {
chrome.tabs.get(tabId, function(tab) {
openViewer(tab && tab.windowId, fileEntries);
});
} else {
// Re-use existing window, if available.
chrome.windows.getLastFocused(function(chromeWindow) {
var windowId = chromeWindow && chromeWindow.id;
if (windowId) {
chrome.windows.update(windowId, { focused: true });
}
openViewer(windowId, fileEntries);
});
}
}
/**
* Open the PDF Viewer for the given list of PDF files.
*
* @param {number} windowId
* @param {Array} fileEntries List of Entry objects (HTML5 FileSystem API)
*/
function openViewer(windowId, fileEntries) {
if (!fileEntries.length) {
return;
}
var fileEntry = fileEntries.shift();
var url = fileEntry.toURL();
// Use drive: alias to get shorter (more human-readable) URLs.
url = url.replace(/^filesystem:chrome-extension:\/\/[a-p]{32}\/external\//,
'drive:');
url = getViewerURL(url);
if (windowId) {
chrome.tabs.create({
windowId: windowId,
active: true,
url: url
}, function() {
openViewer(windowId, fileEntries);
});
} else {
chrome.windows.create({
type: 'normal',
focused: true,
url: url
}, function(chromeWindow) {
openViewer(chromeWindow.id, fileEntries);
});
}
}
})();

View File

@ -0,0 +1,24 @@
<!doctype html>
<!--
Copyright 2012 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.
-->
<script src="chrome.tabs.executeScriptInFrame.js"></script>
<script src="feature-detect.js"></script>
<script src="preserve-referer.js"></script>
<script src="pdfHandler.js"></script>
<script src="extension-router.js"></script>
<script src="pdfHandler-v2.js"></script>
<script src="pdfHandler-vcros.js"></script>
<script src="pageAction/background.js"></script>

View File

@ -0,0 +1,222 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
Copyright 2012 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, Features, saveReferer */
'use strict';
var VIEWER_URL = chrome.extension.getURL('content/web/viewer.html');
function getViewerURL(pdf_url) {
return VIEWER_URL + '?file=' + encodeURIComponent(pdf_url);
}
/**
* @param {Object} details First argument of the webRequest.onHeadersReceived
* event. The property "url" is read.
* @return {boolean} True if the PDF file should be downloaded.
*/
function isPdfDownloadable(details) {
if (details.url.indexOf('pdfjs.action=download') >= 0) {
return true;
}
// Display the PDF viewer regardless of the Content-Disposition header
// if the file is displayed in the main frame.
if (details.type === 'main_frame') {
return false;
}
var cdHeader = (details.responseHeaders &&
getHeaderFromHeaders(details.responseHeaders, 'content-disposition'));
return (cdHeader && /^attachment/i.test(cdHeader.value));
}
/**
* Get the header from the list of headers for a given name.
* @param {Array} headers responseHeaders of webRequest.onHeadersReceived
* @return {undefined|{name: string, value: string}} The header, if found.
*/
function getHeaderFromHeaders(headers, headerName) {
for (var i=0; i<headers.length; ++i) {
var header = headers[i];
if (header.name.toLowerCase() === headerName) {
return header;
}
}
}
/**
* Check if the request is a PDF file.
* @param {Object} details First argument of the webRequest.onHeadersReceived
* event. The properties "responseHeaders" and "url"
* are read.
* @return {boolean} True if the resource is a PDF file.
*/
function isPdfFile(details) {
var header = getHeaderFromHeaders(details.responseHeaders, 'content-type');
if (header) {
var headerValue = header.value.toLowerCase().split(';',1)[0].trim();
return (headerValue === 'application/pdf' ||
headerValue === 'application/octet-stream' &&
details.url.toLowerCase().indexOf('.pdf') > 0);
}
}
/**
* Takes a set of headers, and set "Content-Disposition: attachment".
* @param {Object} details First argument of the webRequest.onHeadersReceived
* event. The property "responseHeaders" is read and
* modified if needed.
* @return {Object|undefined} The return value for the onHeadersReceived event.
* Object with key "responseHeaders" if the headers
* have been modified, undefined otherwise.
*/
function getHeadersWithContentDispositionAttachment(details) {
var headers = details.responseHeaders;
var cdHeader = getHeaderFromHeaders(headers, 'content-disposition');
if (!cdHeader) {
cdHeader = {name: 'Content-Disposition'};
headers.push(cdHeader);
}
if (!/^attachment/i.test(cdHeader.value)) {
cdHeader.value = 'attachment' + cdHeader.value.replace(/^[^;]+/i, '');
return { responseHeaders: headers };
}
}
chrome.webRequest.onHeadersReceived.addListener(
function(details) {
if (details.method !== 'GET') {
// Don't intercept POST requests until http://crbug.com/104058 is fixed.
return;
}
if (!isPdfFile(details)) {
return;
}
if (isPdfDownloadable(details)) {
// Force download by ensuring that Content-Disposition: attachment is set
return getHeadersWithContentDispositionAttachment(details);
}
var viewerUrl = getViewerURL(details.url);
// Implemented in preserve-referer.js
saveReferer(details);
// Replace frame with viewer
if (Features.webRequestRedirectUrl) {
return { redirectUrl: viewerUrl };
}
// Aww.. redirectUrl is not yet supported, so we have to use a different
// method as fallback (Chromium <35).
if (details.frameId === 0) {
// Main frame. Just replace the tab and be done!
chrome.tabs.update(details.tabId, {
url: viewerUrl
});
return { cancel: true };
} else {
// Sub frame. Requires some more work...
// The navigation will be cancelled at the end of the webRequest cycle.
chrome.webNavigation.onErrorOccurred.addListener(function listener(nav) {
if (nav.tabId !== details.tabId || nav.frameId !== details.frameId) {
return;
}
chrome.webNavigation.onErrorOccurred.removeListener(listener);
// Locate frame and insert viewer
chrome.tabs.executeScriptInFrame(details.tabId, {
frameId: details.frameId,
code: 'location.href = ' + JSON.stringify(viewerUrl) + ';'
}, function(result) {
if (!result) {
console.warn('Frame not found! Opening viewer in new tab...');
chrome.tabs.create({
url: viewerUrl
});
}
});
}, {
url: [{ urlEquals: details.url.split('#', 1)[0] }]
});
// Prevent frame from rendering by using X-Frame-Options.
// Do not use { cancel: true }, because that makes the frame inaccessible
// to the content script that has to replace the frame's URL.
return {
responseHeaders: [{
name: 'X-Content-Type-Options',
value: 'nosniff'
}, {
name: 'X-Frame-Options',
value: 'deny'
}]
};
}
// Immediately abort the request, because the frame that initiated the
// request will be replaced with the PDF Viewer (within a split second).
},
{
urls: [
'<all_urls>'
],
types: ['main_frame', 'sub_frame']
},
['blocking','responseHeaders']);
chrome.webRequest.onBeforeRequest.addListener(
function onBeforeRequestForFTP(details) {
if (!Features.extensionSupportsFTP) {
chrome.webRequest.onBeforeRequest.removeListener(onBeforeRequestForFTP);
return;
}
if (isPdfDownloadable(details)) {
return;
}
var viewerUrl = getViewerURL(details.url);
return { redirectUrl: viewerUrl };
},
{
urls: [
'ftp://*/*.pdf',
'ftp://*/*.PDF'
],
types: ['main_frame', 'sub_frame']
},
['blocking']);
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
if (isPdfDownloadable(details)) {
return;
}
// NOTE: The manifest file has declared an empty content script
// at file://*/* to make sure that the viewer can load the PDF file
// through XMLHttpRequest. Necessary to deal with http://crbug.com/302548
var viewerUrl = getViewerURL(details.url);
return { redirectUrl: viewerUrl };
},
{
urls: [
'file://*/*.pdf',
'file://*/*.PDF'
],
types: ['main_frame', 'sub_frame']
},
['blocking']);

View File

@ -0,0 +1,80 @@
{
"type": "object",
"properties": {
"showPreviousViewOnLoad": {
"title": "Show previous position of PDF upon load",
"description": "Whether to view PDF documents in the last page and position upon opening the viewer.",
"type": "boolean",
"default": true
},
"defaultZoomValue": {
"title": "Default zoom level",
"description": "Default zoom level of the viewer. Accepted values: 'auto', 'page-actual', 'page-width', 'page-height', 'page-fit', or a zoom level in percents.",
"type": "string",
"pattern": "|auto|page-actual|page-width|page-height|page-fit|[0-9]+\\.?[0-9]*(,[0-9]+\\.?[0-9]*){0,2}",
"default": ""
},
"sidebarViewOnLoad": {
"title": "Sidebar state on load",
"description": "Controls the state of the sidebar upon load.\n 0 = do not show sidebar.\n 1 = show thumbnails in sidebar.\n 2 = show document outline in sidebar.\n 3 = Show attachments in sidebar.",
"type": "integer",
"enum": [
0,
1,
2,
3
],
"default": 0
},
"enableHandToolOnLoad": {
"title": "Activate Hand tool by default",
"description": "Whether to activate the hand tool by default.",
"type": "boolean",
"default": false
},
"enableWebGL": {
"title": "Enable WebGL",
"description": "Whether to enable WebGL.",
"type": "boolean",
"default": false
},
"pdfBugEnabled": {
"title": "Enable debugging tools",
"description": "Whether to enable debugging tools.",
"type": "boolean",
"default": false
},
"disableRange": {
"title": "Disable range requests",
"description": "Whether to disable range requests (not recommended).",
"type": "boolean",
"default": false
},
"disableStream": {
"title": "Disable streaming for requests",
"description": "Whether to disable streaming for requests (not recommended).",
"type": "boolean",
"default": false
},
"disableAutoFetch": {
"type": "boolean",
"default": false
},
"disableFontFace": {
"title": "Disable @font-face",
"description": "Whether to disable @font-face and fall back to canvas rendering (this is more resource-intensive).",
"type": "boolean",
"default": false
},
"disableTextLayer": {
"title": "Disable text selection layer",
"description": "Whether to disable the text selection layer.",
"type": "boolean",
"default": false
},
"useOnlyCssZoom": {
"type": "boolean",
"default": false
}
}
}

View File

@ -0,0 +1,143 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
Copyright 2015 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, getHeaderFromHeaders */
/* exported saveReferer */
'use strict';
/**
* This file is one part of the Referer persistency implementation. The other
* part resides in chromecom.js.
*
* This file collects request headers for every http(s) request, and temporarily
* stores the request headers in a dictionary. Upon completion of the request
* (success or failure), the headers are discarded.
* pdfHandler.js will call saveReferer(details) when it is about to redirect to
* the viewer. Upon calling saveReferer, the Referer header is extracted from
* the request headers and saved.
*
* When the viewer is opened, it opens a port ("chromecom-referrer"). This port
* is used to set up the webRequest listeners that stick the Referer headers to
* the HTTP requests created by this extension. When the port is disconnected,
* the webRequest listeners and the referrer information is discarded.
*
* See setReferer in chromecom.js for more explanation of this logic.
*/
// Remembers the request headers for every http(s) page request for the duration
// of the request.
var g_requestHeaders = {};
// g_referrers[tabId][frameId] = referrer of PDF frame.
var g_referrers = {};
(function() {
var requestFilter = {
urls: ['*://*/*'],
types: ['main_frame', 'sub_frame']
};
chrome.webRequest.onSendHeaders.addListener(function(details) {
g_requestHeaders[details.requestId] = details.requestHeaders;
}, requestFilter, ['requestHeaders']);
chrome.webRequest.onBeforeRedirect.addListener(forgetHeaders, requestFilter);
chrome.webRequest.onCompleted.addListener(forgetHeaders, requestFilter);
chrome.webRequest.onErrorOccurred.addListener(forgetHeaders, requestFilter);
function forgetHeaders(details) {
delete g_requestHeaders[details.requestId];
}
})();
/**
* @param {object} details - onHeadersReceived event data.
*/
function saveReferer(details) {
var referer = g_requestHeaders[details.requestId] &&
getHeaderFromHeaders(g_requestHeaders[details.requestId], 'referer');
referer = referer && referer.value || '';
if (!g_referrers[details.tabId]) {
g_referrers[details.tabId] = {};
}
g_referrers[details.tabId][details.frameId] = referer;
}
chrome.tabs.onRemoved.addListener(function(tabId) {
delete g_referrers[tabId];
});
// This method binds a webRequest event handler which adds the Referer header
// to matching PDF resource requests (only if the Referer is non-empty). The
// handler is removed as soon as the PDF viewer frame is unloaded.
chrome.runtime.onConnect.addListener(function onReceivePort(port) {
if (port.name !== 'chromecom-referrer') {
return;
}
// Note: sender.frameId is only set in Chrome 41+.
if (!('frameId' in port.sender)) {
port.disconnect();
return;
}
var tabId = port.sender.tab.id;
var frameId = port.sender.frameId;
// If the PDF is viewed for the first time, then the referer will be set here.
var referer = g_referrers[tabId] && g_referrers[tabId][frameId] || '';
port.onMessage.addListener(function(data) {
// If the viewer was opened directly (without opening a PDF URL first), then
// the background script does not know about g_referrers, but the viewer may
// know about the referer if stored in the history state (see chromecom.js).
if (data.referer) {
referer = data.referer;
}
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
if (referer) {
// Only add a blocking request handler if the referer has to be rewritten.
chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {
urls: [data.requestUrl],
types: ['xmlhttprequest'],
tabId: tabId
}, ['blocking', 'requestHeaders']);
}
// Acknowledge the message, and include the latest referer for this frame.
port.postMessage(referer);
});
// The port is only disconnected when the other end reloads.
port.onDisconnect.addListener(function() {
if (g_referrers[tabId]) {
delete g_referrers[tabId][frameId];
}
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
});
function onBeforeSendHeaders(details) {
if (details.frameId !== frameId) {
return;
}
var headers = details.requestHeaders;
var refererHeader = getHeaderFromHeaders(headers, 'referer');
if (!refererHeader) {
refererHeader = {name: 'Referer'};
headers.push(refererHeader);
} else if (refererHeader.value &&
refererHeader.value.lastIndexOf('chrome-extension:', 0) !== 0) {
// Sanity check. If the referer is set, and the value is not the URL of
// this extension, then the request was not initiated by this extension.
return;
}
refererHeader.value = referer;
return {requestHeaders: headers};
}
});