Vectorize website settings icons in omnibox
[chromium-blink-merge.git] / components / dom_distiller / core / javascript / dom_distiller_viewer.js
blob5a15f3d6dbac1b9b902af59990f07ffd88e14f4d
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   if (theme == "sepia") {
89     cssClass = "sepia";
90   } else if (theme == "dark") {
91     cssClass = "dark";
92   } else {
93     cssClass = "light";
94   }
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";
115   }
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);
130       }
131     } else if (isLastPage && typeof this.colorShuffle != "undefined") {
132       clearInterval(this.colorShuffle);
133     }
134   };
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'.
142  */
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('contentWrap').style.paddingBottom = '120px';
153   document.getElementById('feedbackContainer').style.display = 'block';
154   var mediaQuery = window.matchMedia("print");
155   mediaQuery.addListener(function (query) {
156     if (query.matches) {
157       document.getElementById('contentWrap').style.paddingBottom = '0px';
158       document.getElementById('feedbackContainer').style.display = 'none';
159     } else {
160       document.getElementById('contentWrap').style.paddingBottom = '120px';
161       document.getElementById('feedbackContainer').style.display = 'block';
162     }
163   });
167  * Send feedback about this distilled article.
168  * @param good True if the feedback was positive, false if negative.
169  */
170 function sendFeedback(good) {
171   var img = document.createElement('img');
172   if (good) {
173     img.src = '/feedbackgood';
174   } else {
175     img.src = '/feedbackbad';
176   }
177   img.style.display = "none";
178   document.body.appendChild(img);
181 // Add a listener to the "View Original" link to report opt-outs.
182 document.getElementById('closeReaderView').addEventListener('click',
183     function(e) {
184       var img = document.createElement('img');
185       img.src = "/vieworiginal";
186       img.style.display = "none";
187       document.body.appendChild(img);
188     }, true);
190 document.getElementById('feedbackYes').addEventListener('click', function(e) {
191   sendFeedback(true);
192   document.getElementById('feedbackContainer').className += " fadeOut";
193 }, true);
195 document.getElementById('feedbackNo').addEventListener('click', function(e) {
196   sendFeedback(false);
197   document.getElementById('feedbackContainer').className += " fadeOut";
198 }, true);
200 document.getElementById('feedbackContainer').addEventListener('animationend',
201     function(e) {
202       document.getElementById('feedbackContainer').style.display = 'none';
203       // Close the gap where the feedback form was.
204       var contentWrap = document.getElementById('contentWrap');
205       contentWrap.style.transition = '0.5s';
206       contentWrap.style.paddingBottom = '0px';
207     }, true);
209 document.getElementById('contentWrap').addEventListener('transitionend',
210     function(e) {
211       var contentWrap = document.getElementById('contentWrap');
212       contentWrap.style.transition = '';
213     }, true);
215 updateToolbarColor();
217 var pincher = (function() {
218   'use strict';
219   // When users pinch in Reader Mode, the page would zoom in or out as if it
220   // is a normal web page allowing user-zoom. At the end of pinch gesture, the
221   // page would do text reflow. These pinch-to-zoom and text reflow effects
222   // are not native, but are emulated using CSS and JavaScript.
223   //
224   // In order to achieve near-native zooming and panning frame rate, fake 3D
225   // transform is used so that the layer doesn't repaint for each frame.
226   //
227   // After the text reflow, the web content shown in the viewport should
228   // roughly be the same paragraph before zooming.
229   //
230   // The control point of font size is the html element, so that both "em" and
231   // "rem" are adjusted.
232   //
233   // TODO(wychen): Improve scroll position when elementFromPoint is body.
235   var pinching = false;
236   var fontSizeAnchor = 1.0;
238   var focusElement = null;
239   var focusPos = 0;
240   var initClientMid;
242   var clampedScale = 1;
244   var lastSpan;
245   var lastClientMid;
247   var scale = 1;
248   var shiftX;
249   var shiftY;
251   // The zooming speed relative to pinching speed.
252   var FONT_SCALE_MULTIPLIER = 0.5;
253   var MIN_SPAN_LENGTH = 20;
255   // The font size is guaranteed to be in px.
256   var baseSize =
257       parseFloat(getComputedStyle(document.documentElement).fontSize);
259   var refreshTransform = function() {
260     var slowedScale = Math.exp(Math.log(scale) * FONT_SCALE_MULTIPLIER);
261     clampedScale = Math.max(0.4, Math.min(2.5, fontSizeAnchor * slowedScale));
263     // Use "fake" 3D transform so that the layer is not repainted.
264     // With 2D transform, the frame rate would be much lower.
265     document.body.style.transform =
266         'translate3d(' + shiftX + 'px,' +
267                          shiftY + 'px, 0px)' +
268         'scale(' + clampedScale/fontSizeAnchor + ')';
269   };
271   function endPinch() {
272     pinching = false;
274     document.body.style.transformOrigin = '';
275     document.body.style.transform = '';
276     document.documentElement.style.fontSize = clampedScale * baseSize + "px";
278     var rect = focusElement.getBoundingClientRect();
279     var targetTop = focusPos * (rect.bottom - rect.top) + rect.top +
280         document.body.scrollTop - (initClientMid.y + shiftY);
281     document.body.scrollTop = targetTop;
282   }
284   function touchSpan(e) {
285     var count = e.touches.length;
286     var mid = touchClientMid(e);
287     var sum = 0;
288     for (var i = 0; i < count; i++) {
289       var dx = (e.touches[i].clientX - mid.x);
290       var dy = (e.touches[i].clientY - mid.y);
291       sum += Math.hypot(dx, dy);
292     }
293     // Avoid very small span.
294     return Math.max(MIN_SPAN_LENGTH, sum/count);
295   }
297   function touchClientMid(e) {
298     var count = e.touches.length;
299     var sumX = 0;
300     var sumY = 0;
301     for (var i = 0; i < count; i++) {
302       sumX += e.touches[i].clientX;
303       sumY += e.touches[i].clientY;
304     }
305     return {x: sumX/count, y: sumY/count};
306   }
308   function touchPageMid(e) {
309     var clientMid = touchClientMid(e);
310     return {x: clientMid.x - e.touches[0].clientX + e.touches[0].pageX,
311             y: clientMid.y - e.touches[0].clientY + e.touches[0].pageY};
312   }
314   return {
315     handleTouchStart: function(e) {
316       if (e.touches.length < 2) return;
317       e.preventDefault();
319       var span = touchSpan(e);
320       var clientMid = touchClientMid(e);
322       if (e.touches.length > 2) {
323         lastSpan = span;
324         lastClientMid = clientMid;
325         refreshTransform();
326         return;
327       }
329       scale = 1;
330       shiftX = 0;
331       shiftY = 0;
333       pinching = true;
334       fontSizeAnchor =
335           parseFloat(getComputedStyle(document.documentElement).fontSize) /
336           baseSize;
338       var pinchOrigin = touchPageMid(e);
339       document.body.style.transformOrigin =
340           pinchOrigin.x + 'px ' + pinchOrigin.y  + 'px';
342       // Try to preserve the pinching center after text reflow.
343       // This is accurate to the HTML element level.
344       focusElement = document.elementFromPoint(clientMid.x, clientMid.y);
345       var rect = focusElement.getBoundingClientRect();
346       initClientMid = clientMid;
347       focusPos = (initClientMid.y - rect.top) / (rect.bottom - rect.top);
349       lastSpan = span;
350       lastClientMid = clientMid;
352       refreshTransform();
353     },
355     handleTouchMove: function(e) {
356       if (!pinching) return;
357       if (e.touches.length < 2) return;
358       e.preventDefault();
360       var span = touchSpan(e);
361       var clientMid = touchClientMid(e);
363       scale *= touchSpan(e) / lastSpan;
364       shiftX += clientMid.x - lastClientMid.x;
365       shiftY += clientMid.y - lastClientMid.y;
367       refreshTransform();
369       lastSpan = span;
370       lastClientMid = clientMid;
371     },
373     handleTouchEnd: function(e) {
374       if (!pinching) return;
375       e.preventDefault();
377       var span = touchSpan(e);
378       var clientMid = touchClientMid(e);
380       if (e.touches.length >= 2) {
381         lastSpan = span;
382         lastClientMid = clientMid;
383         refreshTransform();
384         return;
385       }
387       endPinch();
388     },
390     handleTouchCancel: function(e) {
391       endPinch();
392     },
394     reset: function() {
395       scale = 1;
396       shiftX = 0;
397       shiftY = 0;
398       clampedScale = 1;
399       document.documentElement.style.fontSize = clampedScale * baseSize + "px";
400     },
402     status: function() {
403       return {
404         scale: scale,
405         clampedScale: clampedScale,
406         shiftX: shiftX,
407         shiftY: shiftY
408       };
409     }
410   };
411 }());
413 window.addEventListener('touchstart', pincher.handleTouchStart, false);
414 window.addEventListener('touchmove', pincher.handleTouchMove, false);
415 window.addEventListener('touchend', pincher.handleTouchEnd, false);
416 window.addEventListener('touchcancel', pincher.handleTouchCancel, false);