Switch TestFrameNavigationObserver to DidCommitProvisionalLoadForFrame.
[chromium-blink-merge.git] / components / dom_distiller / core / javascript / dom_distiller_viewer.js
blob4a80bba65d38d28c1aba4551d05cf11e7e575149
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 function addToPage(html) {
6 var div = document.createElement('div');
7 div.innerHTML = html;
8 document.getElementById('content').appendChild(div);
9 fillYouTubePlaceholders();
12 function fillYouTubePlaceholders() {
13 var placeholders = document.getElementsByClassName('embed-placeholder');
14 for (var i = 0; i < placeholders.length; i++) {
15 if (!placeholders[i].hasAttribute('data-type') ||
16 placeholders[i].getAttribute('data-type') != 'youtube' ||
17 !placeholders[i].hasAttribute('data-id')) {
18 continue;
20 var embed = document.createElement('iframe');
21 var url = 'http://www.youtube.com/embed/' +
22 placeholders[i].getAttribute('data-id');
23 embed.setAttribute('class', 'youtubeIframe');
24 embed.setAttribute('src', url);
25 embed.setAttribute('type', 'text/html');
26 embed.setAttribute('frameborder', '0');
28 var parent = placeholders[i].parentElement;
29 var container = document.createElement('div');
30 container.setAttribute('class', 'youtubeContainer');
31 container.appendChild(embed);
33 parent.replaceChild(container, placeholders[i]);
37 function showLoadingIndicator(isLastPage) {
38 document.getElementById('loadingIndicator').className =
39 isLastPage ? 'hidden' : 'visible';
40 updateLoadingIndicator(isLastPage);
43 // Sets the title. The title will be exposed with a simple animation. This
44 // should only be used when the title was not included in the initial html.
45 function setTitle(title) {
46 var holder = document.getElementById('titleHolder');
47 var collapse = document.getElementById('titleCollapse');
49 collapse.style.height = "0px";
51 holder.textContent = title;
52 var newHeight = Math.max(90, holder.getBoundingClientRect().height);
54 collapse.style.transition = "height 0.2s";
55 collapse.style.height = newHeight + "px";
58 // Maps JS Font Family to CSS class and then changes body class name.
59 // CSS classes must agree with distilledpage.css.
60 function useFontFamily(fontFamily) {
61 var cssClass;
62 if (fontFamily == "serif") {
63 cssClass = "serif";
64 } else if (fontFamily == "monospace") {
65 cssClass = "monospace";
66 } else {
67 cssClass = "sans-serif";
69 // Relies on the classname order of the body being Theme class, then Font
70 // Family class.
71 var themeClass = document.body.className.split(" ")[0];
72 document.body.className = themeClass + " " + cssClass;
75 // Maps JS theme to CSS class and then changes body class name.
76 // CSS classes must agree with distilledpage.css.
77 function useTheme(theme) {
78 var cssClass;
79 if (theme == "sepia") {
80 cssClass = "sepia";
81 } else if (theme == "dark") {
82 cssClass = "dark";
83 } else {
84 cssClass = "light";
86 // Relies on the classname order of the body being Theme class, then Font
87 // Family class.
88 var fontFamilyClass = document.body.className.split(" ")[1];
89 document.body.className = cssClass + " " + fontFamilyClass;
92 var updateLoadingIndicator = function() {
93 var colors = ["red", "yellow", "green", "blue"];
94 return function(isLastPage) {
95 if (!isLastPage && typeof this.colorShuffle == "undefined") {
96 var loader = document.getElementById("loader");
97 if (loader) {
98 var colorIndex = -1;
99 this.colorShuffle = setInterval(function() {
100 colorIndex = (colorIndex + 1) % colors.length;
101 loader.className = colors[colorIndex];
102 }, 600);
104 } else if (isLastPage && typeof this.colorShuffle != "undefined") {
105 clearInterval(this.colorShuffle);
108 }();
111 * Show the distiller feedback form.
112 * @param questionText The i18n text for the feedback question.
113 * @param yesText The i18n text for the feedback answer 'YES'.
114 * @param noText The i18n text for the feedback answer 'NO'.
116 function showFeedbackForm(questionText, yesText, noText) {
117 document.getElementById('feedbackYes').innerText = yesText;
118 document.getElementById('feedbackNo').innerText = noText;
119 document.getElementById('feedbackQuestion').innerText = questionText;
121 document.getElementById('feedbackContainer').style.display = 'block';
125 * Send feedback about this distilled article.
126 * @param good True if the feedback was positive, false if negative.
128 function sendFeedback(good) {
129 var img = document.createElement('img');
130 if (good) {
131 img.src = '/feedbackgood';
132 } else {
133 img.src = '/feedbackbad';
135 img.style.display = "none";
136 document.body.appendChild(img);
139 // Add a listener to the "View Original" link to report opt-outs.
140 document.getElementById('showOriginal').addEventListener('click', function(e) {
141 var img = document.createElement('img');
142 img.src = "/vieworiginal";
143 img.style.display = "none";
144 document.body.appendChild(img);
145 }, true);
147 document.getElementById('feedbackYes').addEventListener('click', function(e) {
148 sendFeedback(true);
149 document.getElementById('feedbackContainer').className += " fadeOut";
150 }, true);
152 document.getElementById('feedbackNo').addEventListener('click', function(e) {
153 sendFeedback(false);
154 document.getElementById('feedbackContainer').className += " fadeOut";
155 }, true);
157 document.getElementById('feedbackContainer').addEventListener('animationend',
158 function(e) {
159 document.getElementById('feedbackContainer').style.display = 'none';
160 // Close the gap where the feedback form was.
161 var contentWrap = document.getElementById('contentWrap');
162 contentWrap.style.transition = '0.5s';
163 contentWrap.style.paddingBottom = '0px';
164 }, true);
166 document.getElementById('contentWrap').addEventListener('transitionend',
167 function(e) {
168 var contentWrap = document.getElementById('contentWrap');
169 contentWrap.style.transition = '';
170 }, true);
172 var pincher = (function() {
173 'use strict';
174 // When users pinch in Reader Mode, the page would zoom in or out as if it
175 // is a normal web page allowing user-zoom. At the end of pinch gesture, the
176 // page would do text reflow. These pinch-to-zoom and text reflow effects
177 // are not native, but are emulated using CSS and JavaScript.
179 // In order to achieve near-native zooming and panning frame rate, fake 3D
180 // transform is used so that the layer doesn't repaint for each frame.
182 // After the text reflow, the web content shown in the viewport should
183 // roughly be the same paragraph before zooming.
185 // The control point of font size is the html element, so that both "em" and
186 // "rem" are adjusted.
188 // TODO(wychen): Improve scroll position when elementFromPoint is body.
190 var pinching = false;
191 var fontSizeAnchor = 1.0;
193 var focusElement = null;
194 var focusPos = 0;
195 var initClientMid;
197 var clampedScale = 1;
199 var lastSpan;
200 var lastClientMid;
202 var scale = 1;
203 var shiftX;
204 var shiftY;
206 // The zooming speed relative to pinching speed.
207 var FONT_SCALE_MULTIPLIER = 0.5;
208 var MIN_SPAN_LENGTH = 20;
210 // The font size is guaranteed to be in px.
211 var baseSize =
212 parseFloat(getComputedStyle(document.documentElement).fontSize);
214 var refreshTransform = function() {
215 var slowedScale = Math.exp(Math.log(scale) * FONT_SCALE_MULTIPLIER);
216 clampedScale = Math.max(0.4, Math.min(2.5, fontSizeAnchor * slowedScale));
218 // Use "fake" 3D transform so that the layer is not repainted.
219 // With 2D transform, the frame rate would be much lower.
220 document.body.style.transform =
221 'translate3d(' + shiftX + 'px,' +
222 shiftY + 'px, 0px)' +
223 'scale(' + clampedScale/fontSizeAnchor + ')';
226 function endPinch() {
227 pinching = false;
229 document.body.style.transformOrigin = '';
230 document.body.style.transform = '';
231 document.documentElement.style.fontSize = clampedScale * baseSize + "px";
233 var rect = focusElement.getBoundingClientRect();
234 var targetTop = focusPos * (rect.bottom - rect.top) + rect.top +
235 document.body.scrollTop - (initClientMid.y + shiftY);
236 document.body.scrollTop = targetTop;
239 function touchSpan(e) {
240 var count = e.touches.length;
241 var mid = touchClientMid(e);
242 var sum = 0;
243 for (var i = 0; i < count; i++) {
244 var dx = (e.touches[i].clientX - mid.x);
245 var dy = (e.touches[i].clientY - mid.y);
246 sum += Math.hypot(dx, dy);
248 // Avoid very small span.
249 return Math.max(MIN_SPAN_LENGTH, sum/count);
252 function touchClientMid(e) {
253 var count = e.touches.length;
254 var sumX = 0;
255 var sumY = 0;
256 for (var i = 0; i < count; i++) {
257 sumX += e.touches[i].clientX;
258 sumY += e.touches[i].clientY;
260 return {x: sumX/count, y: sumY/count};
263 function touchPageMid(e) {
264 var clientMid = touchClientMid(e);
265 return {x: clientMid.x - e.touches[0].clientX + e.touches[0].pageX,
266 y: clientMid.y - e.touches[0].clientY + e.touches[0].pageY};
269 return {
270 handleTouchStart: function(e) {
271 if (e.touches.length < 2) return;
272 e.preventDefault();
274 var span = touchSpan(e);
275 var clientMid = touchClientMid(e);
277 if (e.touches.length > 2) {
278 lastSpan = span;
279 lastClientMid = clientMid;
280 refreshTransform();
281 return;
284 scale = 1;
285 shiftX = 0;
286 shiftY = 0;
288 pinching = true;
289 fontSizeAnchor =
290 parseFloat(getComputedStyle(document.documentElement).fontSize) /
291 baseSize;
293 var pinchOrigin = touchPageMid(e);
294 document.body.style.transformOrigin =
295 pinchOrigin.x + 'px ' + pinchOrigin.y + 'px';
297 // Try to preserve the pinching center after text reflow.
298 // This is accurate to the HTML element level.
299 focusElement = document.elementFromPoint(clientMid.x, clientMid.y);
300 var rect = focusElement.getBoundingClientRect();
301 initClientMid = clientMid;
302 focusPos = (initClientMid.y - rect.top) / (rect.bottom - rect.top);
304 lastSpan = span;
305 lastClientMid = clientMid;
307 refreshTransform();
310 handleTouchMove: function(e) {
311 if (!pinching) return;
312 if (e.touches.length < 2) return;
313 e.preventDefault();
315 var span = touchSpan(e);
316 var clientMid = touchClientMid(e);
318 scale *= touchSpan(e) / lastSpan;
319 shiftX += clientMid.x - lastClientMid.x;
320 shiftY += clientMid.y - lastClientMid.y;
322 refreshTransform();
324 lastSpan = span;
325 lastClientMid = clientMid;
328 handleTouchEnd: function(e) {
329 if (!pinching) return;
330 e.preventDefault();
332 var span = touchSpan(e);
333 var clientMid = touchClientMid(e);
335 if (e.touches.length >= 2) {
336 lastSpan = span;
337 lastClientMid = clientMid;
338 refreshTransform();
339 return;
342 endPinch();
345 handleTouchCancel: function(e) {
346 endPinch();
349 reset: function() {
350 scale = 1;
351 shiftX = 0;
352 shiftY = 0;
353 clampedScale = 1;
354 document.documentElement.style.fontSize = clampedScale * baseSize + "px";
357 status: function() {
358 return {
359 scale: scale,
360 clampedScale: clampedScale,
361 shiftX: shiftX,
362 shiftY: shiftY
366 }());
368 window.addEventListener('touchstart', pincher.handleTouchStart, false);
369 window.addEventListener('touchmove', pincher.handleTouchMove, false);
370 window.addEventListener('touchend', pincher.handleTouchEnd, false);
371 window.addEventListener('touchcancel', pincher.handleTouchCancel, false);