Linux: Depend on liberation-fonts package for RPMs.
[chromium-blink-merge.git] / components / dom_distiller / core / javascript / dom_distiller_viewer.js
blobc44c6a87a534f2a9513ce4a74a73ab4b608a77f4
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This variable will be changed by iOS scripts.
6 var distiller_on_ios = false;
8 function addToPage(html) {
9 var div = document.createElement('div');
10 div.innerHTML = html;
11 document.getElementById('content').appendChild(div);
12 fillYouTubePlaceholders();
14 if (typeof navigate_on_initial_content_load !== 'undefined' &&
15 navigate_on_initial_content_load) {
16 navigate_on_initial_content_load = false;
17 setTimeout(function() {
18 window.location = window.location + "#loaded";
19 }, 0);
23 function fillYouTubePlaceholders() {
24 var placeholders = document.getElementsByClassName('embed-placeholder');
25 for (var i = 0; i < placeholders.length; i++) {
26 if (!placeholders[i].hasAttribute('data-type') ||
27 placeholders[i].getAttribute('data-type') != 'youtube' ||
28 !placeholders[i].hasAttribute('data-id')) {
29 continue;
31 var embed = document.createElement('iframe');
32 var url = 'http://www.youtube.com/embed/' +
33 placeholders[i].getAttribute('data-id');
34 embed.setAttribute('class', 'youtubeIframe');
35 embed.setAttribute('src', url);
36 embed.setAttribute('type', 'text/html');
37 embed.setAttribute('frameborder', '0');
39 var parent = placeholders[i].parentElement;
40 var container = document.createElement('div');
41 container.setAttribute('class', 'youtubeContainer');
42 container.appendChild(embed);
44 parent.replaceChild(container, placeholders[i]);
48 function showLoadingIndicator(isLastPage) {
49 document.getElementById('loadingIndicator').className =
50 isLastPage ? 'hidden' : 'visible';
51 updateLoadingIndicator(isLastPage);
54 // Sets the title.
55 function setTitle(title) {
56 var holder = document.getElementById('titleHolder');
58 holder.textContent = title;
59 document.title = title;
62 // Set the text direction of the document ('ltr', 'rtl', or 'auto').
63 function setTextDirection(direction) {
64 document.body.setAttribute('dir', direction);
67 // Maps JS Font Family to CSS class and then changes body class name.
68 // CSS classes must agree with distilledpage.css.
69 function useFontFamily(fontFamily) {
70 var cssClass;
71 if (fontFamily == "serif") {
72 cssClass = "serif";
73 } else if (fontFamily == "monospace") {
74 cssClass = "monospace";
75 } else {
76 cssClass = "sans-serif";
78 // Relies on the classname order of the body being Theme class, then Font
79 // Family class.
80 var themeClass = document.body.className.split(" ")[0];
81 document.body.className = themeClass + " " + cssClass;
84 // Maps JS theme to CSS class and then changes body class name.
85 // CSS classes must agree with distilledpage.css.
86 function useTheme(theme) {
87 var cssClass;
88 if (theme == "sepia") {
89 cssClass = "sepia";
90 } else if (theme == "dark") {
91 cssClass = "dark";
92 } else {
93 cssClass = "light";
95 // Relies on the classname order of the body being Theme class, then Font
96 // Family class.
97 var fontFamilyClass = document.body.className.split(" ")[1];
98 document.body.className = cssClass + " " + fontFamilyClass;
100 updateToolbarColor();
103 function updateToolbarColor() {
104 // Relies on the classname order of the body being Theme class, then Font
105 // Family class.
106 var themeClass = document.body.className.split(" ")[0];
108 var toolbarColor;
109 if (themeClass == "sepia") {
110 toolbarColor = "#BF9A73";
111 } else if (themeClass == "dark") {
112 toolbarColor = "#1A1A1A";
113 } else {
114 toolbarColor = "#F5F5F5";
116 document.getElementById('theme-color').content = toolbarColor;
119 var updateLoadingIndicator = function() {
120 var colors = ["red", "yellow", "green", "blue"];
121 return function(isLastPage) {
122 if (!isLastPage && typeof this.colorShuffle == "undefined") {
123 var loader = document.getElementById("loader");
124 if (loader) {
125 var colorIndex = -1;
126 this.colorShuffle = setInterval(function() {
127 colorIndex = (colorIndex + 1) % colors.length;
128 loader.className = colors[colorIndex];
129 }, 600);
131 } else if (isLastPage && typeof this.colorShuffle != "undefined") {
132 clearInterval(this.colorShuffle);
135 }();
138 * Show the distiller feedback form.
139 * @param questionText The i18n text for the feedback question.
140 * @param yesText The i18n text for the feedback answer 'YES'.
141 * @param noText The i18n text for the feedback answer 'NO'.
143 function showFeedbackForm(questionText, yesText, noText) {
144 // If the distiller is running on iOS, do not show the feedback form. This
145 // variable is set in distiller_viewer.cc before this function is run.
146 if (distiller_on_ios) return;
148 document.getElementById('feedbackYes').innerText = yesText;
149 document.getElementById('feedbackNo').innerText = noText;
150 document.getElementById('feedbackQuestion').innerText = questionText;
152 document.getElementById('feedbackContainer').classList.remove("hidden");
156 * Send feedback about this distilled article.
157 * @param good True if the feedback was positive, false if negative.
159 function sendFeedback(good) {
160 var img = document.createElement('img');
161 if (good) {
162 img.src = '/feedbackgood';
163 } else {
164 img.src = '/feedbackbad';
166 img.style.display = "none";
167 document.body.appendChild(img);
170 // Add a listener to the "View Original" link to report opt-outs.
171 document.getElementById('closeReaderView').addEventListener('click',
172 function(e) {
173 var img = document.createElement('img');
174 img.src = "/vieworiginal";
175 img.style.display = "none";
176 document.body.appendChild(img);
177 }, true);
179 document.getElementById('feedbackYes').addEventListener('click', function(e) {
180 sendFeedback(true);
181 document.getElementById('feedbackContainer').className += " fadeOut";
182 }, true);
184 document.getElementById('feedbackNo').addEventListener('click', function(e) {
185 sendFeedback(false);
186 document.getElementById('feedbackContainer').className += " fadeOut";
187 }, true);
189 document.getElementById('feedbackContainer').addEventListener('animationend',
190 function(e) {
191 var feedbackContainer = document.getElementById('feedbackContainer');
192 feedbackContainer.classList.remove("fadeOut");
193 feedbackContainer.className += " hidden";
194 document.getElementById('contentWrap').style.paddingBottom = '120px';
195 setTimeout(function() {
196 // Close the gap where the feedback form was.
197 var contentWrap = document.getElementById('contentWrap');
198 contentWrap.style.transition = '0.5s';
199 contentWrap.style.paddingBottom = '';
200 }, 0);
201 }, true);
203 document.getElementById('contentWrap').addEventListener('transitionend',
204 function(e) {
205 var contentWrap = document.getElementById('contentWrap');
206 contentWrap.style.transition = '';
207 }, true);
209 updateToolbarColor();
211 var pincher = (function() {
212 'use strict';
213 // When users pinch in Reader Mode, the page would zoom in or out as if it
214 // is a normal web page allowing user-zoom. At the end of pinch gesture, the
215 // page would do text reflow. These pinch-to-zoom and text reflow effects
216 // are not native, but are emulated using CSS and JavaScript.
218 // In order to achieve near-native zooming and panning frame rate, fake 3D
219 // transform is used so that the layer doesn't repaint for each frame.
221 // After the text reflow, the web content shown in the viewport should
222 // roughly be the same paragraph before zooming.
224 // The control point of font size is the html element, so that both "em" and
225 // "rem" are adjusted.
227 // TODO(wychen): Improve scroll position when elementFromPoint is body.
229 var pinching = false;
230 var fontSizeAnchor = 1.0;
232 var focusElement = null;
233 var focusPos = 0;
234 var initClientMid;
236 var clampedScale = 1;
238 var lastSpan;
239 var lastClientMid;
241 var scale = 1;
242 var shiftX;
243 var shiftY;
245 // The zooming speed relative to pinching speed.
246 var FONT_SCALE_MULTIPLIER = 0.5;
247 var MIN_SPAN_LENGTH = 20;
249 // The font size is guaranteed to be in px.
250 var baseSize =
251 parseFloat(getComputedStyle(document.documentElement).fontSize);
253 var refreshTransform = function() {
254 var slowedScale = Math.exp(Math.log(scale) * FONT_SCALE_MULTIPLIER);
255 clampedScale = Math.max(0.4, Math.min(2.5, fontSizeAnchor * slowedScale));
257 // Use "fake" 3D transform so that the layer is not repainted.
258 // With 2D transform, the frame rate would be much lower.
259 document.body.style.transform =
260 'translate3d(' + shiftX + 'px,' +
261 shiftY + 'px, 0px)' +
262 'scale(' + clampedScale/fontSizeAnchor + ')';
265 function endPinch() {
266 pinching = false;
268 document.body.style.transformOrigin = '';
269 document.body.style.transform = '';
270 document.documentElement.style.fontSize = clampedScale * baseSize + "px";
272 var rect = focusElement.getBoundingClientRect();
273 var targetTop = focusPos * (rect.bottom - rect.top) + rect.top +
274 document.body.scrollTop - (initClientMid.y + shiftY);
275 document.body.scrollTop = targetTop;
278 function touchSpan(e) {
279 var count = e.touches.length;
280 var mid = touchClientMid(e);
281 var sum = 0;
282 for (var i = 0; i < count; i++) {
283 var dx = (e.touches[i].clientX - mid.x);
284 var dy = (e.touches[i].clientY - mid.y);
285 sum += Math.hypot(dx, dy);
287 // Avoid very small span.
288 return Math.max(MIN_SPAN_LENGTH, sum/count);
291 function touchClientMid(e) {
292 var count = e.touches.length;
293 var sumX = 0;
294 var sumY = 0;
295 for (var i = 0; i < count; i++) {
296 sumX += e.touches[i].clientX;
297 sumY += e.touches[i].clientY;
299 return {x: sumX/count, y: sumY/count};
302 function touchPageMid(e) {
303 var clientMid = touchClientMid(e);
304 return {x: clientMid.x - e.touches[0].clientX + e.touches[0].pageX,
305 y: clientMid.y - e.touches[0].clientY + e.touches[0].pageY};
308 return {
309 handleTouchStart: function(e) {
310 if (e.touches.length < 2) return;
311 e.preventDefault();
313 var span = touchSpan(e);
314 var clientMid = touchClientMid(e);
316 if (e.touches.length > 2) {
317 lastSpan = span;
318 lastClientMid = clientMid;
319 refreshTransform();
320 return;
323 scale = 1;
324 shiftX = 0;
325 shiftY = 0;
327 pinching = true;
328 fontSizeAnchor =
329 parseFloat(getComputedStyle(document.documentElement).fontSize) /
330 baseSize;
332 var pinchOrigin = touchPageMid(e);
333 document.body.style.transformOrigin =
334 pinchOrigin.x + 'px ' + pinchOrigin.y + 'px';
336 // Try to preserve the pinching center after text reflow.
337 // This is accurate to the HTML element level.
338 focusElement = document.elementFromPoint(clientMid.x, clientMid.y);
339 var rect = focusElement.getBoundingClientRect();
340 initClientMid = clientMid;
341 focusPos = (initClientMid.y - rect.top) / (rect.bottom - rect.top);
343 lastSpan = span;
344 lastClientMid = clientMid;
346 refreshTransform();
349 handleTouchMove: function(e) {
350 if (!pinching) return;
351 if (e.touches.length < 2) return;
352 e.preventDefault();
354 var span = touchSpan(e);
355 var clientMid = touchClientMid(e);
357 scale *= touchSpan(e) / lastSpan;
358 shiftX += clientMid.x - lastClientMid.x;
359 shiftY += clientMid.y - lastClientMid.y;
361 refreshTransform();
363 lastSpan = span;
364 lastClientMid = clientMid;
367 handleTouchEnd: function(e) {
368 if (!pinching) return;
369 e.preventDefault();
371 var span = touchSpan(e);
372 var clientMid = touchClientMid(e);
374 if (e.touches.length >= 2) {
375 lastSpan = span;
376 lastClientMid = clientMid;
377 refreshTransform();
378 return;
381 endPinch();
384 handleTouchCancel: function(e) {
385 endPinch();
388 reset: function() {
389 scale = 1;
390 shiftX = 0;
391 shiftY = 0;
392 clampedScale = 1;
393 document.documentElement.style.fontSize = clampedScale * baseSize + "px";
396 status: function() {
397 return {
398 scale: scale,
399 clampedScale: clampedScale,
400 shiftX: shiftX,
401 shiftY: shiftY
405 }());
407 window.addEventListener('touchstart', pincher.handleTouchStart, false);
408 window.addEventListener('touchmove', pincher.handleTouchMove, false);
409 window.addEventListener('touchend', pincher.handleTouchEnd, false);
410 window.addEventListener('touchcancel', pincher.handleTouchCancel, false);