script.js revision ffc725fe4b4fa1024feb22b1d1f76c24febadcdb
1/* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18var BLOCKED_SRC_ATTR = "blocked-src"; 19 20/** 21 * Returns the page offset of an element. 22 * 23 * @param {Element} element The element to return the page offset for. 24 * @return {left: number, top: number} A tuple including a left and top value representing 25 * the page offset of the element. 26 */ 27function getTotalOffset(el) { 28 var result = { 29 left: 0, 30 top: 0 31 }; 32 var parent = el; 33 34 while (parent) { 35 result.left += parent.offsetLeft; 36 result.top += parent.offsetTop; 37 parent = parent.offsetParent; 38 } 39 40 return result; 41} 42 43/** 44 * Walks up the DOM starting at a given element, and returns an element that has the 45 * specified class name or null. 46 */ 47function up(el, className) { 48 var parent = el; 49 while (parent) { 50 if (parent.classList && parent.classList.contains(className)) { 51 break; 52 } 53 parent = parent.parentNode; 54 } 55 return parent || null; 56} 57 58function toggleQuotedText(e) { 59 var toggleElement = e.target; 60 var elidedTextElement = toggleElement.nextSibling; 61 var isHidden = getComputedStyle(elidedTextElement).display == 'none'; 62 toggleElement.innerHTML = isHidden ? MSG_HIDE_ELIDED : MSG_SHOW_ELIDED; 63 elidedTextElement.style.display = isHidden ? 'block' : 'none'; 64 65 // Revealing the elided text should normalize it to fit-width to prevent 66 // this message from blowing out the conversation width. 67 if (isHidden) { 68 normalizeElementWidths([elidedTextElement]); 69 } 70 71 measurePositions(); 72} 73 74function collapseAllQuotedText() { 75 collapseQuotedText(document.documentElement); 76} 77 78function collapseQuotedText(elt) { 79 var i; 80 var elements = elt.getElementsByClassName("elided-text"); 81 var elidedElement, toggleElement; 82 for (i = 0; i < elements.length; i++) { 83 elidedElement = elements[i]; 84 toggleElement = document.createElement("div"); 85 toggleElement.className = "mail-elided-text"; 86 toggleElement.innerHTML = MSG_SHOW_ELIDED; 87 toggleElement.onclick = toggleQuotedText; 88 elidedElement.parentNode.insertBefore(toggleElement, elidedElement); 89 } 90} 91 92function normalizeAllMessageWidths() { 93 normalizeElementWidths(document.querySelectorAll(".expanded > .mail-message-content")); 94} 95 96/* 97 * Normalizes the width of all elements supplied to the document body's overall width. 98 * Narrower elements are zoomed in, and wider elements are zoomed out. 99 * This method is idempotent. 100 */ 101function normalizeElementWidths(elements) { 102 var i; 103 var el; 104 var documentWidth = document.body.offsetWidth; 105 var newZoom, oldZoom; 106 107 for (i = 0; i < elements.length; i++) { 108 el = elements[i]; 109 oldZoom = el.style.zoom; 110 // reset any existing normalization 111 if (oldZoom) { 112 el.style.zoom = 1; 113 } 114 newZoom = documentWidth / el.scrollWidth; 115 el.style.zoom = newZoom; 116 } 117} 118 119function hideUnsafeImages() { 120 var i, bodyCount; 121 var j, imgCount; 122 var body, image; 123 var images; 124 var showImages; 125 var bodies = document.getElementsByClassName("mail-message-content"); 126 for (i = 0, bodyCount = bodies.length; i < bodyCount; i++) { 127 body = bodies[i]; 128 showImages = body.classList.contains("mail-show-images"); 129 130 images = body.getElementsByTagName("img"); 131 for (j = 0, imgCount = images.length; j < imgCount; j++) { 132 image = images[j]; 133 rewriteRelativeImageSrc(image); 134 attachImageLoadListener(image); 135 // TODO: handle inline image attachments for all supported protocols 136 if (!showImages) { 137 blockImage(image); 138 } 139 } 140 } 141} 142 143/** 144 * Changes relative paths to absolute path by pre-pending the account uri 145 * @param {Element} imgElement Image for which the src path will be updated. 146 */ 147function rewriteRelativeImageSrc(imgElement) { 148 var src = imgElement.src; 149 150 // DOC_BASE_URI will always be a unique x-thread:// uri for this particular conversation 151 if (src.indexOf(DOC_BASE_URI) == 0 && (DOC_BASE_URI != CONVERSATION_BASE_URI)) { 152 // The conversation specifies a different base uri than the document 153 src = CONVERSATION_BASE_URI + src.substring(DOC_BASE_URI.length); 154 imgElement.src = src; 155 } 156}; 157 158 159function attachImageLoadListener(imageElement) { 160 // Reset the src attribute to the empty string because onload will only fire if the src 161 // attribute is set after the onload listener. 162 var originalSrc = imageElement.src; 163 imageElement.src = ''; 164 imageElement.onload = imageOnLoad; 165 imageElement.src = originalSrc; 166} 167 168function imageOnLoad(e) { 169 // normalize the quoted text parent if we're in a quoted text block, or else 170 // normalize the parent message content element 171 var parent = up(e.target, "elided-text") || up(e.target, "mail-message-content"); 172 if (parent) { 173 normalizeElementWidths([parent]); 174 } 175 measurePositions(); 176} 177 178function blockImage(imageElement) { 179 var src = imageElement.src; 180 if (src.indexOf("http://") == 0 || src.indexOf("https://") == 0 || 181 src.indexOf("content://") == 0) { 182 imageElement.setAttribute(BLOCKED_SRC_ATTR, src); 183 imageElement.src = "data:"; 184 } 185} 186 187function setWideViewport() { 188 var metaViewport = document.getElementById('meta-viewport'); 189 metaViewport.setAttribute('content', 'width=' + WIDE_VIEWPORT_WIDTH); 190} 191 192// BEGIN Java->JavaScript handlers 193function measurePositions() { 194 var overlayBottoms; 195 var h; 196 var i; 197 var len; 198 199 var expandedSpacerDivs = document.querySelectorAll(".expanded > .spacer"); 200 201 overlayBottoms = new Array(expandedSpacerDivs.length + 1); 202 for (i = 0, len = expandedSpacerDivs.length; i < len; i++) { 203 h = expandedSpacerDivs[i].offsetHeight; 204 // addJavascriptInterface handler only supports string arrays 205 overlayBottoms[i] = "" + (getTotalOffset(expandedSpacerDivs[i]).top + h); 206 } 207 // add an extra one to mark the bottom of the last message 208 overlayBottoms[i] = "" + document.body.offsetHeight; 209 210 window.mail.onWebContentGeometryChange(overlayBottoms); 211} 212 213function unblockImages(messageDomId) { 214 var i, images, imgCount, image, blockedSrc; 215 var msg = document.getElementById(messageDomId); 216 if (!msg) { 217 console.log("can't unblock, no matching message for id: " + messageDomId); 218 return; 219 } 220 images = msg.getElementsByTagName("img"); 221 for (i = 0, imgCount = images.length; i < imgCount; i++) { 222 image = images[i]; 223 blockedSrc = image.getAttribute(BLOCKED_SRC_ATTR); 224 if (blockedSrc) { 225 image.src = blockedSrc; 226 image.removeAttribute(BLOCKED_SRC_ATTR); 227 } 228 } 229} 230 231function setConversationHeaderSpacerHeight(spacerHeight) { 232 var spacer = document.getElementById("conversation-header"); 233 if (!spacer) { 234 console.log("can't set spacer for conversation header"); 235 return; 236 } 237 spacer.style.height = spacerHeight + "px"; 238 measurePositions(); 239} 240 241function setMessageHeaderSpacerHeight(messageDomId, spacerHeight) { 242 var spacer = document.querySelector("#" + messageDomId + " > .mail-message-header"); 243 if (!spacer) { 244 console.log("can't set spacer for message with id: " + messageDomId); 245 return; 246 } 247 spacer.style.height = spacerHeight + "px"; 248 measurePositions(); 249} 250 251function setMessageBodyVisible(messageDomId, isVisible, spacerHeight) { 252 var i, len; 253 var visibility = isVisible ? "block" : "none"; 254 var messageDiv = document.querySelector("#" + messageDomId); 255 var collapsibleDivs = document.querySelectorAll("#" + messageDomId + " > .collapsible"); 256 if (!messageDiv || collapsibleDivs.length == 0) { 257 console.log("can't set body visibility for message with id: " + messageDomId); 258 return; 259 } 260 messageDiv.classList.toggle("expanded"); 261 for (i = 0, len = collapsibleDivs.length; i < len; i++) { 262 collapsibleDivs[i].style.display = visibility; 263 } 264 265 // revealing new content should trigger width normalization, since the initial render 266 // skips collapsed and super-collapsed messages 267 if (isVisible) { 268 normalizeElementWidths(messageDiv.getElementsByClassName("mail-message-content")); 269 } 270 271 setMessageHeaderSpacerHeight(messageDomId, spacerHeight); 272} 273 274function replaceSuperCollapsedBlock(startIndex) { 275 var parent, block, header; 276 277 block = document.querySelector(".mail-super-collapsed-block[index='" + startIndex + "']"); 278 if (!block) { 279 console.log("can't expand super collapsed block at index: " + startIndex); 280 return; 281 } 282 parent = block.parentNode; 283 block.innerHTML = window.mail.getTempMessageBodies(); 284 285 header = block.firstChild; 286 while (header) { 287 parent.insertBefore(header, block); 288 header = block.firstChild; 289 } 290 parent.removeChild(block); 291 measurePositions(); 292} 293 294function onContentReady(event) { 295 window.mail.onContentReady(); 296} 297 298function replaceMessageBodies(messageIds) { 299 var i; 300 var id; 301 var msgContentDiv; 302 303 for (i = 0, len = messageIds.length; i < len; i++) { 304 id = messageIds[i]; 305 msgContentDiv = document.querySelector("#" + id + " > .mail-message-content"); 306 msgContentDiv.innerHTML = window.mail.getMessageBody(id); 307 collapseQuotedText(msgContentDiv); 308 } 309} 310 311// END Java->JavaScript handlers 312 313window.onload = function() { 314 // PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER 315 // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op 316 // animation that immediately runs on page load. The app uses this as a signal that the 317 // content is loaded and ready to draw, since WebView delays firing this event until the 318 // layers are composited and everything is ready to draw. 319 // 320 // This code is conditionally enabled on JB+ by setting the 'initial-load' CSS class on this 321 // dummy element. 322 document.getElementById("initial-load-signal") 323 .addEventListener("webkitAnimationStart", onContentReady, false); 324 document.getElementById("initial-load-signal").style.webkitAnimationName = 'initial-load'; 325}; 326 327collapseAllQuotedText(); 328hideUnsafeImages(); 329normalizeAllMessageWidths(); 330//setWideViewport(); 331measurePositions(); 332 333