Skip to content
This repository was archived by the owner on May 7, 2025. It is now read-only.

Commit 932a67b

Browse files
committed
Merge pull request #34 from readium/feature/rso_mathjax_injection
MathJax and epubReadingSystem early DOM injection by intercepting WebView requests
2 parents 88228a5 + 0b4c0a1 commit 932a67b

File tree

7 files changed

+295
-5
lines changed

7 files changed

+295
-5
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ local.properties
2323
# Eclipse project files
2424
.classpath
2525
#.project
26+
27+
# IDEA project files
28+
.idea

SDKLauncher-Android/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
1414
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
1515

16+
<!-- android:debuggable="true" -->
1617
<application
1718
android:allowBackup="true"
1819
android:icon="@drawable/ic_launcher"

SDKLauncher-Android/assets/reader-payloads/MathJax.js

Lines changed: 107 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* The z-index is to ensure that the bookmark is accessible even if it's overlayed on a highlight/underline of some type */
2+
.comment {
3+
background-image : url("/images/comment_clickable_icon.png");
4+
background-repeat : no-repeat;
5+
opacity : 0.5;
6+
z-index : 10;
7+
}
8+
.hover-comment {
9+
background-image : url("/images/comment_clickable_icon.png");
10+
background-repeat : no-repeat;
11+
opacity : 1.0;
12+
z-index : 10;
13+
}
14+
15+
.bookmark {
16+
position : fixed;
17+
top : "0px";
18+
right : "0px";
19+
background-color: red;
20+
}
21+
22+
.highlight {
23+
position: absolute;
24+
opacity: 0.2;
25+
background-color: red;
26+
}
27+
28+
.hover-highlight {
29+
position: absolute;
30+
opacity: 0.4;
31+
background-color: red;
32+
}
33+
34+
.underline-range {
35+
position: absolute;
36+
}
37+
38+
.underline-range > .underline {
39+
position: relative;
40+
height: 15%;
41+
opacity: 0.2;
42+
background-color: red;
43+
}
44+
45+
.underline-range > .hover-underline {
46+
position: relative;
47+
height: 15%;
48+
opacity: 0.4;
49+
background-color: red;
50+
}
51+
52+
.underline-range > .transparent-part {
53+
position: relative;
54+
height: 85%;
55+
background-color: transparent;
56+
}

SDKLauncher-Android/assets/readium-shared-js/reader.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@
4545
$(document).ready(function () {
4646
"use strict";
4747

48-
ReadiumSDK.reader = new ReadiumSDK.Views.ReaderView({el:"#viewport"});
48+
ReadiumSDK.reader = new ReadiumSDK.Views.ReaderView({
49+
el:"#viewport",
50+
annotationCSSUrl: '/readium_Annotations.css'
51+
});
4952
ReadiumSDK.trigger(ReadiumSDK.Events.READER_INITIALIZED);
5053

5154
});

SDKLauncher-Android/src/org/readium/sdk/android/launcher/WebViewActivity.java

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import android.app.AlertDialog;
5757
import android.content.DialogInterface;
5858
import android.content.Intent;
59+
import android.content.pm.ApplicationInfo;
5960
import android.graphics.Bitmap;
6061
import android.media.MediaPlayer;
6162
import android.net.Uri;
@@ -86,8 +87,45 @@ public class WebViewActivity extends FragmentActivity implements ViewerSettingsD
8687
private static final String TAG = "WebViewActivity";
8788
private static final String ASSET_PREFIX = "file:///android_asset/readium-shared-js/";
8889
private static final String READER_SKELETON = "file:///android_asset/readium-shared-js/reader.html";
89-
90-
private WebView mWebview;
90+
91+
// Installs "hook" function so that top-level window (application) can later inject the window.navigator.epubReadingSystem into this HTML document's iframe
92+
private static final String INJECT_EPUB_RSO_SCRIPT_1 = "" +
93+
"window.readium_set_epubReadingSystem = function (obj) {" +
94+
"\nwindow.navigator.epubReadingSystem = obj;" +
95+
"\nwindow.readium_set_epubReadingSystem = undefined;" +
96+
"\nvar el1 = document.getElementById(\"readium_epubReadingSystem_inject1\");" +
97+
"\nif (el1 && el1.parentNode) { el1.parentNode.removeChild(el1); }" +
98+
"\nvar el2 = document.getElementById(\"readium_epubReadingSystem_inject2\");" +
99+
"\nif (el2 && el2.parentNode) { el2.parentNode.removeChild(el2); }" +
100+
"\n};";
101+
102+
// Iterate top-level iframes, inject global window.navigator.epubReadingSystem if the expected hook function exists ( readium_set_epubReadingSystem() ).
103+
private static final String INJECT_EPUB_RSO_SCRIPT_2 = "" +
104+
"for (var i = 0; i < window.frames.length; i++) { \n" +
105+
"var iframe = window.frames[i]; \n" +
106+
"if (iframe.readium_set_epubReadingSystem) { \n" +
107+
"iframe.readium_set_epubReadingSystem(window.navigator.epubReadingSystem); \n" +
108+
"}\n" +
109+
"}\n";
110+
// Script tag to inject the "hook" function installer script, added to the head of every epub iframe document
111+
private static final String INJECT_HEAD_EPUB_RSO_1 = "" +
112+
"<script id=\"readium_epubReadingSystem_inject1\" type=\"text/javascript\">\n" +
113+
"//<![CDATA[\n" +
114+
INJECT_EPUB_RSO_SCRIPT_1 + "\n" +
115+
"//]]>\n" +
116+
"</script>";
117+
// Script tag that generates an HTTP request to a fake script => triggers push of window.navigator.epubReadingSystem into this HTML document's iframe
118+
private static final String INJECT_HEAD_EPUB_RSO_2 = ""+
119+
"<script id=\"readium_epubReadingSystem_inject2\" type=\"text/javascript\" " +
120+
"src=\"/%d/readium_epubReadingSystem_inject.js\"> </script>";
121+
// Script tag to load the mathjax script payload, added to the head of epub iframe documents, only if <math> tags are detected
122+
private static final String INJECT_HEAD_MATHJAX = "<script type=\"text/javascript\" src=\"/readium_MathJax.js\"> </script>";
123+
124+
// Location of payloads in the asset folder
125+
private static final String PAYLOAD_MATHJAX_ASSET = "reader-payloads/MathJax.js";
126+
private static final String PAYLOAD_ANNOTATIONS_CSS_ASSET = "reader-payloads/annotations.css";
127+
128+
private WebView mWebview;
91129
private Container mContainer;
92130
private Package mPackage;
93131
private OpenPageRequest mOpenPageRequestData;
@@ -98,13 +136,19 @@ public class WebViewActivity extends FragmentActivity implements ViewerSettingsD
98136

99137
private boolean mIsMoAvailable;
100138
private boolean mIsMoPlaying;
139+
private int mEpubRsoInjectCounter = 0;
101140

102141
@Override
103142
protected void onCreate(Bundle savedInstanceState) {
104143
super.onCreate(savedInstanceState);
105144
setContentView(R.layout.activity_web_view);
106145

107146
mWebview = (WebView) findViewById(R.id.webview);
147+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
148+
&& 0 != (getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) {
149+
mWebview.setWebContentsDebuggingEnabled(true);
150+
}
151+
108152
mPageInfo = (TextView) findViewById(R.id.page_info);
109153
initWebView();
110154

@@ -301,13 +345,56 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
301345
return false;
302346
}
303347

348+
private void evaluateJavascript(final String script) {
349+
runOnUiThread(new Runnable() {
350+
@Override
351+
public void run() {
352+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
353+
mWebview.evaluateJavascript(script, null);
354+
} else {
355+
mWebview.loadUrl("javascript:" + script);
356+
}
357+
}
358+
});
359+
}
360+
304361
@Override
305362
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
306363
Log.d(TAG, "shouldInterceptRequest: " + url);
307364
Uri uri = Uri.parse(url);
308365
if (uri.getScheme().equals("file") && url != "undefined" && url != null) {
309366
String cleanedUrl = cleanResourceUrl(url);
310367
Log.d(TAG, url+" => "+cleanedUrl);
368+
369+
if (cleanedUrl.matches("\\/?\\d*\\/readium_epubReadingSystem_inject.js")) {
370+
Log.d(TAG, "navigator.epubReadingSystem inject ...");
371+
372+
// Fake script requested, this is immediately invoked after epubReadingSystem hook is in place,
373+
// => execute js on the reader.html context to push the global window.navigator.epubReadingSystem into the iframe(s)
374+
evaluateJavascript(INJECT_EPUB_RSO_SCRIPT_2);
375+
return new WebResourceResponse("text/javascript", UTF_8
376+
, new ByteArrayInputStream("(function(){})()".getBytes()));
377+
378+
}
379+
// Special handling for payload requests
380+
else if (cleanedUrl.matches("\\/?readium_MathJax.js")) {
381+
Log.d(TAG, "MathJax.js inject ...");
382+
try {
383+
return new WebResourceResponse("text/javascript", UTF_8
384+
, getAssets().open(PAYLOAD_MATHJAX_ASSET));
385+
} catch (IOException e) {
386+
return super.shouldInterceptRequest(view, url);
387+
}
388+
} else if (cleanedUrl.matches("\\/?readium_Annotations.css")) {
389+
Log.d(TAG, "annotations.css inject ...");
390+
try {
391+
return new WebResourceResponse("text/css", UTF_8
392+
, getAssets().open(PAYLOAD_ANNOTATIONS_CSS_ASSET));
393+
} catch (IOException e) {
394+
return super.shouldInterceptRequest(view, url);
395+
}
396+
}
397+
311398
InputStream data = mPackage.getInputStream(cleanedUrl);
312399
ManifestItem item = mPackage.getManifestItem(cleanedUrl);
313400
if (item != null && item.isHtml()) {
@@ -316,8 +403,22 @@ public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
316403
binary = new byte[data.available()];
317404
data.read(binary);
318405
data.close();
319-
data = new ByteArrayInputStream(HTMLUtil.htmlByReplacingMediaURLsInHTML(new String(binary),
320-
cleanedUrl, "PackageUUID").getBytes());
406+
String htmlText = new String(binary);
407+
String newHtml = HTMLUtil.htmlByReplacingMediaURLsInHTML(htmlText, cleanedUrl, "PackageUUID");
408+
409+
// Set up the script tags to add to the head
410+
String tagsToInjectToHead = INJECT_HEAD_EPUB_RSO_1
411+
// Slightly change fake script src url with an increasing count to prevent caching of the request
412+
+ String.format(INJECT_HEAD_EPUB_RSO_2, ++mEpubRsoInjectCounter);
413+
// Checks for the existance of MathML => request MathJax payload
414+
if (newHtml.contains("<math")) {
415+
tagsToInjectToHead += INJECT_HEAD_MATHJAX;
416+
}
417+
418+
newHtml = HTMLUtil.htmlByInjectingIntoHead(newHtml, tagsToInjectToHead);
419+
//Log.d(TAG, "HTML head inject: " + newHtml);
420+
421+
data = new ByteArrayInputStream(newHtml.getBytes());
321422
} catch (IOException e) {
322423
Log.e(TAG, ""+e.getMessage(), e);
323424
}

SDKLauncher-Android/src/org/readium/sdk/android/launcher/util/HTMLUtil.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
import java.io.File;
3434
import java.text.MessageFormat;
35+
import java.util.regex.Matcher;
36+
import java.util.regex.Pattern;
3537

3638
import android.net.Uri;
3739

@@ -43,6 +45,23 @@
4345
*/
4446
public class HTMLUtil {
4547

48+
public static String htmlByInjectingIntoHead(String html, String headHtmToInsert){
49+
if (html == null || html.length() == 0
50+
|| headHtmToInsert == null || headHtmToInsert.length() == 0){
51+
return html;
52+
}
53+
54+
String pattern = "<head.*>";
55+
Matcher matcher = Pattern.compile(pattern).matcher(html);
56+
57+
if (matcher.find()) {
58+
int headTagEndPos = matcher.end();
59+
return new StringBuilder(html).insert(headTagEndPos, headHtmToInsert).toString();
60+
} else {
61+
return html;
62+
}
63+
}
64+
4665
public static String htmlByReplacingMediaURLsInHTML(String html,
4766
String relativePath, String packageUUID) {
4867
if (html == null || html.length() == 0 || relativePath == null

0 commit comments

Comments
 (0)