Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
Choose a Base Repository
hypothesis/h
40a/h
AFDudley/h
BigBlueHat/h
BinaryStars/h
CCH543/h
Cinemacloud/h
Ericgood/h
FTG-003/h
Forethinker/h
GratefulTony/h
HGldJ1966/h
JJediny/h
John-Williams/h
Laurian/h
LittleFancy/h
MattyQ/h
Mishkin2015/h
RichardLitt/h
Staffan1/h
SteelWagstaff/h
TowerBR/h
VanyTang/h
abigailricarte/h
ackermann/h
alecchap/h
alesarrett/h
alexsegura/h
almereyda/h
alon/h
andzi/h
angelicxsoul/h
ansmoh/h
apurvajalit/h
arjunvasan/h
asdevor/h
bZichett/h
badgettrg/Webmarks
balmas/h
balupton/h
bbarker/h
bennlich/h
benthor/h
blakewest/h
bogste/h
bradparks/h
brittanystoroz/h
buiquangchien/h
cdchapman/h
charblanc/h
chowsamihq/h
chr7stos/Webmarks
chrber/h
chrismPssina/h
christinaphamAD/h
cmbirk/h
codeaudit/h
coolcool21/h
cove/h
csillag/h
danjimilk/h
dannyhope/h
daredream/h
davidmcclure/h
dennisplucinik/h
dezynetechnologies/h
diegodlh/h
djcun95/h
donsequitur/h
edsu/h
eiro10/h
emckean/h
ercchy/h
eshellman/h
fangang123/h
fchasen/h
fcrimins/h
fhirsch/h
ficolo/h
fragkopoulos/h
gauravkeerthi/h
geass/h
gergely-ujvari/h
gitter-badger/h
gnott/h
gobengo/h
gorinovic/h
gus3000/h
hashin/h
helemaalbigt/h
hmstepanek/h
hwasiti/h
hylhero/h
hyperstudio/h
iHDeveloper/h
imeysam/h
jackspaceBerkeley/h
jarey/h
jasdeep/h
jason790/h
jasonzou/j
jazahn/h
jccr/h
jean/h
jeka57/h
jeremydean/h
jermnelson/h
jibe-b/h
jnishiyama/h
jojksd/h
jpadilla/h
jtremback/h
judell/h
juli-so/h
kabacs/h
karissa/h
kaushikvijay/h
kaydoh/h
kill4uk/h
klopiinas/h
klrkdekira/h
koulihong311/h
krassif/h
krstnkngs/h
leoqmp/h
linhua55/h
lucadealfaro/h
lyspooner/h
lyzadanger/h
m1yag1/h
magee/h
mambocab/h
manunymous/h
maraino/h
mari-ja/h
markbarratt/h
martinq/h
mbbaig/h
mcarv63/h
meawoppl/h
meflyup/h
metasj/h
mgasner/h
mgax/h
mollycr/h
mrchrisadams/h
mrienstra/h
mshavlovsky/h
muddasani/h
nagyist/hyphothesis-h
nagyistoce/hypothesis-h
nanxio/h
neozhangthe1/h
ningyifan/h
nkingsley/h
nlholdem/h
nlisgo/h
noscripter/h
nshkuro/h
odnodn/h
oliversauter/h
openbizgit/h
opengovfoundation/h
openstax/hypothesis-server
ouroboros8/h
pablomarti/h
pamo/h
philipn/h
philschatz/h
pinballwonder/h
plainspace/h
raowl/h
rickyhan/h
rmoorman/h
rmtsukuru/h
robertknight/h
rowhit/h
rsarxiv/h
saakaifoundry/h
samrose/h
scharf/h
shepazu/h
sherah/h
shofheinz/h
soapdog/h
ssin122/test-h
st-fresh/h
stuk88/h
sylvanmist/h
tetratorus/h
tilgovi/h
tomnar/h
trivenews/h
truthadjustr/h
utngz/h
voidfiles/h
wenchen/h
yargevad/h
yumatch/h
zshen777/h
Nothing to show
Choose a Head Repository
hypothesis/h
40a/h
AFDudley/h
BigBlueHat/h
BinaryStars/h
CCH543/h
Cinemacloud/h
Ericgood/h
FTG-003/h
Forethinker/h
GratefulTony/h
HGldJ1966/h
JJediny/h
John-Williams/h
Laurian/h
LittleFancy/h
MattyQ/h
Mishkin2015/h
RichardLitt/h
Staffan1/h
SteelWagstaff/h
TowerBR/h
VanyTang/h
abigailricarte/h
ackermann/h
alecchap/h
alesarrett/h
alexsegura/h
almereyda/h
alon/h
andzi/h
angelicxsoul/h
ansmoh/h
apurvajalit/h
arjunvasan/h
asdevor/h
bZichett/h
badgettrg/Webmarks
balmas/h
balupton/h
bbarker/h
bennlich/h
benthor/h
blakewest/h
bogste/h
bradparks/h
brittanystoroz/h
buiquangchien/h
cdchapman/h
charblanc/h
chowsamihq/h
chr7stos/Webmarks
chrber/h
chrismPssina/h
christinaphamAD/h
cmbirk/h
codeaudit/h
coolcool21/h
cove/h
csillag/h
danjimilk/h
dannyhope/h
daredream/h
davidmcclure/h
dennisplucinik/h
dezynetechnologies/h
diegodlh/h
djcun95/h
donsequitur/h
edsu/h
eiro10/h
emckean/h
ercchy/h
eshellman/h
fangang123/h
fchasen/h
fcrimins/h
fhirsch/h
ficolo/h
fragkopoulos/h
gauravkeerthi/h
geass/h
gergely-ujvari/h
gitter-badger/h
gnott/h
gobengo/h
gorinovic/h
gus3000/h
hashin/h
helemaalbigt/h
hmstepanek/h
hwasiti/h
hylhero/h
hyperstudio/h
iHDeveloper/h
imeysam/h
jackspaceBerkeley/h
jarey/h
jasdeep/h
jason790/h
jasonzou/j
jazahn/h
jccr/h
jean/h
jeka57/h
jeremydean/h
jermnelson/h
jibe-b/h
jnishiyama/h
jojksd/h
jpadilla/h
jtremback/h
judell/h
juli-so/h
kabacs/h
karissa/h
kaushikvijay/h
kaydoh/h
kill4uk/h
klopiinas/h
klrkdekira/h
koulihong311/h
krassif/h
krstnkngs/h
leoqmp/h
linhua55/h
lucadealfaro/h
lyspooner/h
lyzadanger/h
m1yag1/h
magee/h
mambocab/h
manunymous/h
maraino/h
mari-ja/h
markbarratt/h
martinq/h
mbbaig/h
mcarv63/h
meawoppl/h
meflyup/h
metasj/h
mgasner/h
mgax/h
mollycr/h
mrchrisadams/h
mrienstra/h
mshavlovsky/h
muddasani/h
nagyist/hyphothesis-h
nagyistoce/hypothesis-h
nanxio/h
neozhangthe1/h
ningyifan/h
nkingsley/h
nlholdem/h
nlisgo/h
noscripter/h
nshkuro/h
odnodn/h
oliversauter/h
openbizgit/h
opengovfoundation/h
openstax/hypothesis-server
ouroboros8/h
pablomarti/h
pamo/h
philipn/h
philschatz/h
pinballwonder/h
plainspace/h
raowl/h
rickyhan/h
rmoorman/h
rmtsukuru/h
robertknight/h
rowhit/h
rsarxiv/h
saakaifoundry/h
samrose/h
scharf/h
shepazu/h
sherah/h
shofheinz/h
soapdog/h
ssin122/test-h
st-fresh/h
stuk88/h
sylvanmist/h
tetratorus/h
tilgovi/h
tomnar/h
trivenews/h
truthadjustr/h
utngz/h
voidfiles/h
wenchen/h
yargevad/h
yumatch/h
zshen777/h
Nothing to show
  • 2 commits
  • 4 files changed
  • 0 commit comments
  • 1 contributor
Commits on Jan 06, 2016
Improve PDF viewer detection in Chrome
Rather than relying on the tab URL containing '.pdf',
check whether the current tab is displaying Chrome's native
PDF viewer and open Hypothesis' PDF.js based viewer in that
case since we the sidebar cannot be injected into the native viewer.

We detect the Chrome PDF viewer by checking for an
<embed ... type="application/pdf"> tag which appears at the top
of the body.

If the user is using a PDF.js or other HTML-based viewer,
then Hypothesis can work with that directly.

An alternative approach would be to detect the mime type of the
loaded document but that requires intercepting all page
load requests in order to read the Content-Type header, which
is not available after the page has loaded OR firing a network
request.

See also this upstream issue: https://code.google.com/p/chromium/issues/detail?id=242575

Fixes #1900
@@ -204,6 +204,7 @@ function HypothesisChromeExtension(dependencies) {
});
return sidebar.injectIntoTab(tab)
.catch(function (err) {
console.error('Failed to inject Hypothesis Sidebar:', err);
tabErrors.setTabError(tab.id, err);
state.errorTab(tab.id);
});
@@ -3,6 +3,29 @@
var blocklist = require('../../../static/scripts/blocklist');
var errors = require('./errors');
var settings = require('./settings');
var util = require('./util');
var CONTENT_TYPE_HTML = 'HTML';
var CONTENT_TYPE_PDF = 'PDF';
// a function which is executed as a content script
// to determine the type of content being displayed in a tab
function detectContentType() {
// check if this is the Chrome PDF viewer
if (document.querySelector('embed[type="application/pdf"]')) {
return {
type: 'PDF',
};
} else {
return {
type: 'HTML',
};
}
}
function toIIFEString(fn) {
return '(' + fn.toString() + ')()';
}
/* The SidebarInjector is used to deploy and remove the Hypothesis sidebar
* from tabs. It also deals with loading PDF documents into the PDF.js viewer
@@ -22,6 +45,8 @@ function SidebarInjector(chromeTabs, dependencies) {
var isAllowedFileSchemeAccess = dependencies.isAllowedFileSchemeAccess;
var extensionURL = dependencies.extensionURL;
var executeScriptFn = util.promisify(chromeTabs.executeScript);
if (typeof extensionURL !== 'function') {
throw new TypeError('extensionURL must be a function');
}
@@ -72,8 +97,20 @@ function SidebarInjector(chromeTabs, dependencies) {
return PDF_VIEWER_URL + '?file=' + encodeURIComponent(url);
}
function isPDFURL(url) {
return url.toLowerCase().indexOf('.pdf') > 0;
function detectTabContentType(tab) {
if (isPDFViewerURL(tab.url)) {
return Promise.resolve(CONTENT_TYPE_PDF);
}
if (!isSupportedURL(tab.url)) {
return Promise.resolve(CONTENT_TYPE_HTML);
}
return executeScriptFn(tab.id, {
code: toIIFEString(detectContentType)
}).then(function (frameResults) {
return frameResults[0].type;
});
}
function isPDFViewerURL(url) {
@@ -85,22 +122,33 @@ function SidebarInjector(chromeTabs, dependencies) {
}
function isSupportedURL(url) {
var SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ftp:'];
// Injection of content scripts is limited to a small number of protocols,
// see https://developer.chrome.com/extensions/match_patterns
var parsedURL = new URL(url);
var SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ftp:', 'file:'];
return SUPPORTED_PROTOCOLS.some(function (protocol) {
return url.indexOf(protocol) === 0;
return parsedURL.protocol === protocol;
});
}
function injectIntoLocalDocument(tab) {
if (isPDFURL(tab.url)) {
return injectIntoLocalPDF(tab);
} else {
return Promise.reject(new errors.LocalFileError('Local non-PDF files are not supported'));
}
return detectTabContentType(tab).then(function (type) {
if (type === CONTENT_TYPE_PDF) {
return injectIntoLocalPDF(tab);
} else {
return Promise.reject(new errors.LocalFileError('Local non-PDF files are not supported'));
}
});
}
function injectIntoRemoteDocument(tab) {
return isPDFURL(tab.url) ? injectIntoPDF(tab) : injectIntoHTML(tab);
return detectTabContentType(tab).then(function (type) {
if (type === CONTENT_TYPE_PDF) {
return injectIntoPDF(tab);
} else {
return injectIntoHTML(tab);
}
});
}
function injectIntoPDF(tab) {
@@ -0,0 +1,44 @@
function getLastError() {
if (typeof chrome !== 'undefined' && chrome.extension) {
return chrome.extension.lastError;
} else {
return undefined;
}
}
/**
* Converts an async Chrome API into a function
* which returns a promise.
*
* Usage:
* var apiFn = promisify(chrome.someModule.aFunction);
* apiFn(arg1, arg2)
* .then(function (result) { ...handle success })
* .catch(function (err) { ...handle error })
*
*
* @param fn A Chrome API function whose last argument is a callback
* which is invoked with the result of the query. When this callback
* is invoked, the promise is rejected if chrome.extension.lastError
* is set or resolved with the first argument to the callback otherwise.
*/
function promisify(fn) {
return function () {
var args = [].slice.call(arguments);
var result = new Promise(function (resolve, reject) {
fn.apply(this, args.concat(function (result) {
var lastError = getLastError();
if (lastError) {
reject(lastError);
} else {
resolve(result);
}
}));
});
return result;
};
}
module.exports = {
promisify: promisify,
};
@@ -7,10 +7,31 @@ describe('SidebarInjector', function () {
var fakeChromeTabs;
var fakeFileAccess;
// the content type that the detection script injected into
// the page should report ('HTML' or 'PDF')
var contentType;
// the return value from the content script which checks whether
// the sidebar has already been injected into the page
var isAlreadyInjected;
beforeEach(function () {
contentType = 'HTML';
isAlreadyInjected = false;
sidebarInstalled = false;
var executeScriptSpy = sinon.spy(function (tabId, details, callback) {
if (details.code.match(/window.annotator/)) {
callback([isAlreadyInjected]);
} else if (details.code.match(/detectContentType/)) {
callback([{type: contentType}]);
} else {
callback([false]);
}
});
fakeChromeTabs = {
update: sinon.stub(),
executeScript: sinon.stub()
executeScript: executeScriptSpy,
};
fakeFileAccess = sinon.stub().yields(true);
@@ -29,13 +50,7 @@ describe('SidebarInjector', function () {
}
describe('.injectIntoTab', function () {
beforeEach(function () {
// Handle loading the config.
fakeChromeTabs.executeScript.withArgs(1, {code: 'window.annotator'}).yields([false]);
fakeChromeTabs.executeScript.yields([]);
});
var protocols = ['chrome:', 'chrome-devtools:', 'chrome-extension'];
var protocols = ['chrome:', 'chrome-devtools:', 'chrome-extension:'];
protocols.forEach(function (protocol) {
it('bails early when trying to load an unsupported ' + protocol + ' url', function () {
var spy = fakeChromeTabs.executeScript;
@@ -52,6 +67,7 @@ describe('SidebarInjector', function () {
describe('when viewing a remote PDF', function () {
it('injects hypothesis into the page', function () {
contentType = 'PDF';
var spy = fakeChromeTabs.update.yields({tab: 1});
var url = 'http://example.com/foo.pdf';
@@ -69,7 +85,6 @@ describe('SidebarInjector', function () {
var url = 'http://example.com/foo.html';
return injector.injectIntoTab({id: 1, url: url}).then(function() {
assert.callCount(spy, 2);
assert.calledWith(spy, 1, {
code: sinon.match('/public/config.js')
});
@@ -85,6 +100,7 @@ describe('SidebarInjector', function () {
it('loads the PDFjs viewer', function () {
var spy = fakeChromeTabs.update.yields([]);
var url = 'file://foo.pdf';
contentType = 'PDF';
return injector.injectIntoTab({id: 1, url: url}).then(
function () {
@@ -100,6 +116,7 @@ describe('SidebarInjector', function () {
describe('when file access is disabled', function () {
beforeEach(function () {
fakeFileAccess.yields(false);
contentType = 'PDF';
});
it('returns an error', function () {
@@ -125,7 +142,9 @@ describe('SidebarInjector', function () {
var url = 'file://foo.html';
var promise = injector.injectIntoTab({id: 1, url: url});
return promise.then(assertReject, function (err) {
assert.notCalled(fakeChromeTabs.executeScript);
assert.isFalse(fakeChromeTabs.executeScript.calledWith(1, {
code: sinon.match(/config\.js/),
}));
});
});
});
@@ -239,7 +258,7 @@ describe('SidebarInjector', function () {
});
});
var protocols = ['chrome:', 'chrome-devtools:', 'chrome-extension'];
var protocols = ['chrome:', 'chrome-devtools:', 'chrome-extension:'];
protocols.forEach(function (protocol) {
it('bails early when trying to unload an unsupported ' + protocol + ' url', function () {
var spy = fakeChromeTabs.executeScript;
@@ -266,9 +285,9 @@ describe('SidebarInjector', function () {
describe('when viewing an HTML page', function () {
it('injects a destroy script into the page', function () {
var stub = fakeChromeTabs.executeScript.yields([true]);
isAlreadyInjected = true;
return injector.removeFromTab({id: 1, url: 'http://example.com/foo.html'}).then(function () {
assert.calledWith(stub, 1, {
assert.calledWith(fakeChromeTabs.executeScript, 1, {
code: sinon.match('/public/destroy.js')
});
});

No commit comments for this range