Revert "Reland c91b178b07b0d - Delete dead signin code (SigninGlobalError)"
[chromium-blink-merge.git] / components / dom_distiller / core / javascript / dom_distiller_viewer.js
blob915134af6ffcda692b5b184ea5c544543f18a1ea
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);
20   }
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;
30     }
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]);
45   }
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";
77   }
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   var toolbarColor;
89   if (theme == "sepia") {
90     cssClass = "sepia";
91     toolbarColor = "#BF9A73";
92   } else if (theme == "dark") {
93     cssClass = "dark";
94     toolbarColor = "#1A1A1A";
95   } else {
96     cssClass = "light";
97     toolbarColor = "#F5F5F5";
98   }
99   // Relies on the classname order of the body being Theme class, then Font
100   // Family class.
101   var fontFamilyClass = document.body.className.split(" ")[1];
102   document.body.className = cssClass + " " + fontFamilyClass;
104   document.getElementById('theme-color').content = toolbarColor;
107 var updateLoadingIndicator = function() {
108   var colors = ["red", "yellow", "green", "blue"];
109   return function(isLastPage) {
110     if (!isLastPage && typeof this.colorShuffle == "undefined") {
111       var loader = document.getElementById("loader");
112       if (loader) {
113         var colorIndex = -1;
114         this.colorShuffle = setInterval(function() {
115           colorIndex = (colorIndex + 1) % colors.length;
116           loader.className = colors[colorIndex];
117         }, 600);
118       }
119     } else if (isLastPage && typeof this.colorShuffle != "undefined") {
120       clearInterval(this.colorShuffle);
121     }
122   };
123 }();
126  * Show the distiller feedback form.
127  * @param questionText The i18n text for the feedback question.
128  * @param yesText The i18n text for the feedback answer 'YES'.
129  * @param noText The i18n text for the feedback answer 'NO'.
130  */
131 function showFeedbackForm(questionText, yesText, noText) {
132   // If the distiller is running on iOS, do not show the feedback form. This
133   // variable is set in distiller_viewer.cc before this function is run.
134   if (distiller_on_ios) return;
136   document.getElementById('feedbackYes').innerText = yesText;
137   document.getElementById('feedbackNo').innerText = noText;
138   document.getElementById('feedbackQuestion').innerText = questionText;
140   document.getElementById('contentWrap').style.paddingBottom = '120px';
141   document.getElementById('feedbackContainer').style.display = 'block';
142   var mediaQuery = window.matchMedia("print");
143   mediaQuery.addListener(function (query) {
144     if (query.matches) {
145       document.getElementById('contentWrap').style.paddingBottom = '0px';
146       document.getElementById('feedbackContainer').style.display = 'none';
147     } else {
148       document.getElementById('contentWrap').style.paddingBottom = '120px';
149       document.getElementById('feedbackContainer').style.display = 'block';
150     }
151   });
155  * Send feedback about this distilled article.
156  * @param good True if the feedback was positive, false if negative.
157  */
158 function sendFeedback(good) {
159   var img = document.createElement('img');
160   if (good) {
161     img.src = '/feedbackgood';
162   } else {
163     img.src = '/feedbackbad';
164   }
165   img.style.display = "none";
166   document.body.appendChild(img);
169 // Add a listener to the "View Original" link to report opt-outs.
170 document.getElementById('closeReaderView').addEventListener('click',
171     function(e) {
172       var img = document.createElement('img');
173       img.src = "/vieworiginal";
174       img.style.display = "none";
175       document.body.appendChild(img);
176     }, true);
178 document.getElementById('feedbackYes').addEventListener('click', function(e) {
179   sendFeedback(true);
180   document.getElementById('feedbackContainer').className += " fadeOut";
181 }, true);
183 document.getElementById('feedbackNo').addEventListener('click', function(e) {
184   sendFeedback(false);
185   document.getElementById('feedbackContainer').className += " fadeOut";
186 }, true);
188 document.getElementById('feedbackContainer').addEventListener('animationend',
189     function(e) {
190       document.getElementById('feedbackContainer').style.display = 'none';
191       // Close the gap where the feedback form was.
192       var contentWrap = document.getElementById('contentWrap');
193       contentWrap.style.transition = '0.5s';
194       contentWrap.style.paddingBottom = '0px';
195     }, true);
197 document.getElementById('contentWrap').addEventListener('transitionend',
198     function(e) {
199       var contentWrap = document.getElementById('contentWrap');
200       contentWrap.style.transition = '';
201     }, true);
203 var pincher = (function() {
204   'use strict';
205   // When users pinch in Reader Mode, the page would zoom in or out as if it
206   // is a normal web page allowing user-zoom. At the end of pinch gesture, the
207   // page would do text reflow. These pinch-to-zoom and text reflow effects
208   // are not native, but are emulated using CSS and JavaScript.
209   //
210   // In order to achieve near-native zooming and panning frame rate, fake 3D
211   // transform is used so that the layer doesn't repaint for each frame.
212   //
213   // After the text reflow, the web content shown in the viewport should
214   // roughly be the same paragraph before zooming.
215   //
216   // The control point of font size is the html element, so that both "em" and
217   // "rem" are adjusted.
218   //
219   // TODO(wychen): Improve scroll position when elementFromPoint is body.
221   var pinching = false;
222   var fontSizeAnchor = 1.0;
224   var focusElement = null;
225   var focusPos = 0;
226   var initClientMid;
228   var clampedScale = 1;
230   var lastSpan;
231   var lastClientMid;
233   var scale = 1;
234   var shiftX;
235   var shiftY;
237   // The zooming speed relative to pinching speed.
238   var FONT_SCALE_MULTIPLIER = 0.5;
239   var MIN_SPAN_LENGTH = 20;
241   // The font size is guaranteed to be in px.
242   var baseSize =
243       parseFloat(getComputedStyle(document.documentElement).fontSize);
245   var refreshTransform = function() {
246     var slowedScale = Math.exp(Math.log(scale) * FONT_SCALE_MULTIPLIER);
247     clampedScale = Math.max(0.4, Math.min(2.5, fontSizeAnchor * slowedScale));
249     // Use "fake" 3D transform so that the layer is not repainted.
250     // With 2D transform, the frame rate would be much lower.
251     document.body.style.transform =
252         'translate3d(' + shiftX + 'px,' +
253                          shiftY + 'px, 0px)' +
254         'scale(' + clampedScale/fontSizeAnchor + ')';
255   };
257   function endPinch() {
258     pinching = false;
260     document.body.style.transformOrigin = '';
261     document.body.style.transform = '';
262     document.documentElement.style.fontSize = clampedScale * baseSize + "px";
264     var rect = focusElement.getBoundingClientRect();
265     var targetTop = focusPos * (rect.bottom - rect.top) + rect.top +
266         document.body.scrollTop - (initClientMid.y + shiftY);
267     document.body.scrollTop = targetTop;
268   }
270   function touchSpan(e) {
271     var count = e.touches.length;
272     var mid = touchClientMid(e);
273     var sum = 0;
274     for (var i = 0; i < count; i++) {
275       var dx = (e.touches[i].clientX - mid.x);
276       var dy = (e.touches[i].clientY - mid.y);
277       sum += Math.hypot(dx, dy);
278     }
279     // Avoid very small span.
280     return Math.max(MIN_SPAN_LENGTH, sum/count);
281   }
283   function touchClientMid(e) {
284     var count = e.touches.length;
285     var sumX = 0;
286     var sumY = 0;
287     for (var i = 0; i < count; i++) {
288       sumX += e.touches[i].clientX;
289       sumY += e.touches[i].clientY;
290     }
291     return {x: sumX/count, y: sumY/count};
292   }
294   function touchPageMid(e) {
295     var clientMid = touchClientMid(e);
296     return {x: clientMid.x - e.touches[0].clientX + e.touches[0].pageX,
297             y: clientMid.y - e.touches[0].clientY + e.touches[0].pageY};
298   }
300   return {
301     handleTouchStart: function(e) {
302       if (e.touches.length < 2) return;
303       e.preventDefault();
305       var span = touchSpan(e);
306       var clientMid = touchClientMid(e);
308       if (e.touches.length > 2) {
309         lastSpan = span;
310         lastClientMid = clientMid;
311         refreshTransform();
312         return;
313       }
315       scale = 1;
316       shiftX = 0;
317       shiftY = 0;
319       pinching = true;
320       fontSizeAnchor =
321           parseFloat(getComputedStyle(document.documentElement).fontSize) /
322           baseSize;
324       var pinchOrigin = touchPageMid(e);
325       document.body.style.transformOrigin =
326           pinchOrigin.x + 'px ' + pinchOrigin.y  + 'px';
328       // Try to preserve the pinching center after text reflow.
329       // This is accurate to the HTML element level.
330       focusElement = document.elementFromPoint(clientMid.x, clientMid.y);
331       var rect = focusElement.getBoundingClientRect();
332       initClientMid = clientMid;
333       focusPos = (initClientMid.y - rect.top) / (rect.bottom - rect.top);
335       lastSpan = span;
336       lastClientMid = clientMid;
338       refreshTransform();
339     },
341     handleTouchMove: function(e) {
342       if (!pinching) return;
343       if (e.touches.length < 2) return;
344       e.preventDefault();
346       var span = touchSpan(e);
347       var clientMid = touchClientMid(e);
349       scale *= touchSpan(e) / lastSpan;
350       shiftX += clientMid.x - lastClientMid.x;
351       shiftY += clientMid.y - lastClientMid.y;
353       refreshTransform();
355       lastSpan = span;
356       lastClientMid = clientMid;
357     },
359     handleTouchEnd: function(e) {
360       if (!pinching) return;
361       e.preventDefault();
363       var span = touchSpan(e);
364       var clientMid = touchClientMid(e);
366       if (e.touches.length >= 2) {
367         lastSpan = span;
368         lastClientMid = clientMid;
369         refreshTransform();
370         return;
371       }
373       endPinch();
374     },
376     handleTouchCancel: function(e) {
377       endPinch();
378     },
380     reset: function() {
381       scale = 1;
382       shiftX = 0;
383       shiftY = 0;
384       clampedScale = 1;
385       document.documentElement.style.fontSize = clampedScale * baseSize + "px";
386     },
388     status: function() {
389       return {
390         scale: scale,
391         clampedScale: clampedScale,
392         shiftX: shiftX,
393         shiftY: shiftY
394       };
395     }
396   };
397 }());
399 window.addEventListener('touchstart', pincher.handleTouchStart, false);
400 window.addEventListener('touchmove', pincher.handleTouchMove, false);
401 window.addEventListener('touchend', pincher.handleTouchEnd, false);
402 window.addEventListener('touchcancel', pincher.handleTouchCancel, false);