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// the set of Elements currently scheduled for processing in handleAllImageLoads 21// this is an Array, but we treat it like a Set and only insert unique items 22var gImageLoadElements = []; 23 24var gScaleInfo; 25 26/** 27 * Only revert transforms that do an imperfect job of shrinking content if they fail 28 * to shrink by this much. Expressed as a ratio of: 29 * (original width difference : width difference after transforms); 30 */ 31var TRANSFORM_MINIMUM_EFFECTIVE_RATIO = 0.7; 32 33// Don't ship with this on. 34var DEBUG_DISPLAY_TRANSFORMS = false; 35 36var gTransformText = {}; 37 38/** 39 * Returns the page offset of an element. 40 * 41 * @param {Element} element The element to return the page offset for. 42 * @return {left: number, top: number} A tuple including a left and top value representing 43 * the page offset of the element. 44 */ 45function getTotalOffset(el) { 46 var result = { 47 left: 0, 48 top: 0 49 }; 50 var parent = el; 51 52 while (parent) { 53 result.left += parent.offsetLeft; 54 result.top += parent.offsetTop; 55 parent = parent.offsetParent; 56 } 57 58 return result; 59} 60 61/** 62 * Walks up the DOM starting at a given element, and returns an element that has the 63 * specified class name or null. 64 */ 65function up(el, className) { 66 var parent = el; 67 while (parent) { 68 if (parent.classList && parent.classList.contains(className)) { 69 break; 70 } 71 parent = parent.parentNode; 72 } 73 return parent || null; 74} 75 76function getCachedValue(div, property, attrName) { 77 var value; 78 if (div.hasAttribute(attrName)) { 79 value = div.getAttribute(attrName); 80 } else { 81 value = div[property]; 82 div.setAttribute(attrName, value); 83 } 84 return value; 85} 86 87function onToggleClick(e) { 88 toggleQuotedText(e.target); 89 measurePositions(); 90} 91 92function toggleQuotedText(toggleElement) { 93 var elidedTextElement = toggleElement.nextSibling; 94 var isHidden = getComputedStyle(elidedTextElement).display == 'none'; 95 toggleElement.innerHTML = isHidden ? MSG_HIDE_ELIDED : MSG_SHOW_ELIDED; 96 elidedTextElement.style.display = isHidden ? 'block' : 'none'; 97 98 // Revealing the elided text should normalize it to fit-width to prevent 99 // this message from blowing out the conversation width. 100 if (isHidden) { 101 normalizeElementWidths([elidedTextElement]); 102 } 103} 104 105function collapseAllQuotedText() { 106 processQuotedText(document.documentElement, false /* showElided */); 107} 108 109function processQuotedText(elt, showElided) { 110 var i; 111 var elements = elt.getElementsByClassName("elided-text"); 112 var elidedElement, toggleElement; 113 for (i = 0; i < elements.length; i++) { 114 elidedElement = elements[i]; 115 toggleElement = document.createElement("div"); 116 toggleElement.className = "mail-elided-text"; 117 toggleElement.innerHTML = MSG_SHOW_ELIDED; 118 toggleElement.setAttribute("dir", "auto"); 119 toggleElement.onclick = onToggleClick; 120 elidedElement.style.display = 'none'; 121 elidedElement.parentNode.insertBefore(toggleElement, elidedElement); 122 if (showElided) { 123 toggleQuotedText(toggleElement); 124 } 125 } 126} 127 128function isConversationEmpty(bodyDivs) { 129 var i, len; 130 var msgBody; 131 var text; 132 133 // Check if given divs are empty (in appearance), and disable zoom if so. 134 for (i = 0, len = bodyDivs.length; i < len; i++) { 135 msgBody = bodyDivs[i]; 136 // use 'textContent' to exclude markup when determining whether bodies are empty 137 // (fall back to more expensive 'innerText' if 'textContent' isn't implemented) 138 text = msgBody.textContent || msgBody.innerText; 139 if (text.trim().length > 0) { 140 return false; 141 } 142 } 143 return true; 144} 145 146function normalizeAllMessageWidths() { 147 var expandedBodyDivs; 148 var metaViewport; 149 var contentValues; 150 var isEmpty; 151 152 expandedBodyDivs = document.querySelectorAll(".expanded > .mail-message-content"); 153 154 isEmpty = isConversationEmpty(expandedBodyDivs); 155 156 normalizeElementWidths(expandedBodyDivs); 157 158 // assemble a working <meta> viewport "content" value from the base value in the 159 // document, plus any dynamically determined options 160 metaViewport = document.getElementById("meta-viewport"); 161 contentValues = [metaViewport.getAttribute("content")]; 162 if (isEmpty) { 163 contentValues.push(metaViewport.getAttribute("data-zoom-off")); 164 } else { 165 contentValues.push(metaViewport.getAttribute("data-zoom-on")); 166 } 167 metaViewport.setAttribute("content", contentValues.join(",")); 168} 169 170/* 171 * Normalizes the width of all elements supplied to the document body's overall width. 172 * Narrower elements are zoomed in, and wider elements are zoomed out. 173 * This method is idempotent. 174 */ 175function normalizeElementWidths(elements) { 176 var i; 177 var el; 178 var documentWidth; 179 var goalWidth; 180 var origWidth; 181 var newZoom, oldZoom; 182 var outerZoom; 183 var outerDiv; 184 185 documentWidth = document.body.offsetWidth; 186 goalWidth = WEBVIEW_WIDTH; 187 188 for (i = 0; i < elements.length; i++) { 189 el = elements[i]; 190 oldZoom = el.style.zoom; 191 // reset any existing normalization 192 if (oldZoom) { 193 el.style.zoom = 1; 194 } 195 origWidth = el.style.width; 196 el.style.width = goalWidth + "px"; 197 transformContent(el, goalWidth, el.scrollWidth); 198 newZoom = documentWidth / el.scrollWidth; 199 if (NORMALIZE_MESSAGE_WIDTHS) { 200 if (el.classList.contains("mail-message-content")) { 201 outerZoom = 1; 202 } else { 203 outerDiv = up(el, "mail-message-content"); 204 outerZoom = outerDiv ? outerDiv.style.zoom : 1; 205 } 206 el.style.zoom = newZoom / outerZoom; 207 } 208 el.style.width = origWidth; 209 } 210} 211 212function transformContent(el, docWidth, elWidth) { 213 var nodes; 214 var i, len; 215 var index; 216 var newWidth = elWidth; 217 var wStr; 218 var touched; 219 // the format of entries in this array is: 220 // entry := [ undoFunction, undoFunctionThis, undoFunctionParamArray ] 221 var actionLog = []; 222 var node; 223 var done = false; 224 var msgId; 225 var transformText; 226 var existingText; 227 var textElement; 228 var start; 229 var beforeWidth; 230 var tmpActionLog = []; 231 if (elWidth <= docWidth) { 232 return; 233 } 234 235 start = Date.now(); 236 237 if (el.parentElement.classList.contains("mail-message")) { 238 msgId = el.parentElement.id; 239 transformText = "[origW=" + elWidth + "/" + docWidth; 240 } 241 242 // Try munging all divs or textareas with inline styles where the width 243 // is wider than docWidth, and change it to be a max-width. 244 touched = false; 245 nodes = ENABLE_MUNGE_TABLES ? el.querySelectorAll("div[style], textarea[style]") : []; 246 touched = transformBlockElements(nodes, docWidth, actionLog); 247 if (touched) { 248 newWidth = el.scrollWidth; 249 console.log("ran div-width munger on el=" + el + " oldW=" + elWidth + " newW=" + newWidth 250 + " docW=" + docWidth); 251 if (msgId) { 252 transformText += " DIV:newW=" + newWidth; 253 } 254 if (newWidth <= docWidth) { 255 done = true; 256 } 257 } 258 259 if (!done) { 260 // OK, that wasn't enough. Find images with widths and override their widths. 261 nodes = ENABLE_MUNGE_IMAGES ? el.querySelectorAll("img") : []; 262 touched = transformImages(nodes, docWidth, actionLog); 263 if (touched) { 264 newWidth = el.scrollWidth; 265 console.log("ran img munger on el=" + el + " oldW=" + elWidth + " newW=" + newWidth 266 + " docW=" + docWidth); 267 if (msgId) { 268 transformText += " IMG:newW=" + newWidth; 269 } 270 if (newWidth <= docWidth) { 271 done = true; 272 } 273 } 274 } 275 276 if (!done) { 277 // OK, that wasn't enough. Find tables with widths and override their widths. 278 // Also ensure that any use of 'table-layout: fixed' is negated, since using 279 // that with 'width: auto' causes erratic table width. 280 nodes = ENABLE_MUNGE_TABLES ? el.querySelectorAll("table") : []; 281 touched = addClassToElements(nodes, shouldMungeTable, "munged", 282 actionLog); 283 if (touched) { 284 newWidth = el.scrollWidth; 285 console.log("ran table munger on el=" + el + " oldW=" + elWidth + " newW=" + newWidth 286 + " docW=" + docWidth); 287 if (msgId) { 288 transformText += " TABLE:newW=" + newWidth; 289 } 290 if (newWidth <= docWidth) { 291 done = true; 292 } 293 } 294 } 295 296 if (!done) { 297 // OK, that wasn't enough. Try munging all <td> to override any width and nowrap set. 298 beforeWidth = newWidth; 299 nodes = ENABLE_MUNGE_TABLES ? el.querySelectorAll("td") : []; 300 touched = addClassToElements(nodes, null /* mungeAll */, "munged", 301 tmpActionLog); 302 if (touched) { 303 newWidth = el.scrollWidth; 304 console.log("ran td munger on el=" + el + " oldW=" + elWidth + " newW=" + newWidth 305 + " docW=" + docWidth); 306 if (msgId) { 307 transformText += " TD:newW=" + newWidth; 308 } 309 if (newWidth <= docWidth) { 310 done = true; 311 } else if (newWidth == beforeWidth) { 312 // this transform did not improve things, and it is somewhat risky. 313 // back it out, since it's the last transform and we gained nothing. 314 undoActions(tmpActionLog); 315 } else { 316 // the transform WAS effective (although not 100%) 317 // copy the temporary action log entries over as normal 318 for (i = 0, len = tmpActionLog.length; i < len; i++) { 319 actionLog.push(tmpActionLog[i]); 320 } 321 } 322 } 323 } 324 325 // If the transformations shrank the width significantly enough, leave them in place. 326 // We figure that in those cases, the benefits outweight the risk of rendering artifacts. 327 if (!done && (elWidth - newWidth) / (elWidth - docWidth) > 328 TRANSFORM_MINIMUM_EFFECTIVE_RATIO) { 329 console.log("transform(s) deemed effective enough"); 330 done = true; 331 } 332 333 if (done) { 334 if (msgId) { 335 transformText += "]"; 336 existingText = gTransformText[msgId]; 337 if (!existingText) { 338 transformText = "Message transforms: " + transformText; 339 } else { 340 transformText = existingText + " " + transformText; 341 } 342 gTransformText[msgId] = transformText; 343 window.mail.onMessageTransform(msgId, transformText); 344 if (DEBUG_DISPLAY_TRANSFORMS) { 345 textElement = el.firstChild; 346 if (!textElement.classList || !textElement.classList.contains("transform-text")) { 347 textElement = document.createElement("div"); 348 textElement.classList.add("transform-text"); 349 textElement.style.fontSize = "10px"; 350 textElement.style.color = "#ccc"; 351 el.insertBefore(textElement, el.firstChild); 352 } 353 textElement.innerHTML = transformText + "<br>"; 354 } 355 } 356 console.log("munger(s) succeeded, elapsed time=" + (Date.now() - start)); 357 return; 358 } 359 360 // reverse all changes if the width is STILL not narrow enough 361 // (except the width->maxWidth change, which is not particularly destructive) 362 undoActions(actionLog); 363 if (actionLog.length > 0) { 364 console.log("all mungers failed, changes reversed. elapsed time=" + (Date.now() - start)); 365 } 366} 367 368function undoActions(actionLog) { 369 for (i = 0, len = actionLog.length; i < len; i++) { 370 actionLog[i][0].apply(actionLog[i][1], actionLog[i][2]); 371 } 372} 373 374function addClassToElements(nodes, conditionFn, classToAdd, actionLog) { 375 var i, len; 376 var node; 377 var added = false; 378 for (i = 0, len = nodes.length; i < len; i++) { 379 node = nodes[i]; 380 if (!conditionFn || conditionFn(node)) { 381 if (node.classList.contains(classToAdd)) { 382 continue; 383 } 384 node.classList.add(classToAdd); 385 added = true; 386 actionLog.push([node.classList.remove, node.classList, [classToAdd]]); 387 } 388 } 389 return added; 390} 391 392function transformBlockElements(nodes, docWidth, actionLog) { 393 var i, len; 394 var node; 395 var wStr; 396 var index; 397 var touched = false; 398 399 for (i = 0, len = nodes.length; i < len; i++) { 400 node = nodes[i]; 401 wStr = node.style.width || node.style.minWidth; 402 index = wStr ? wStr.indexOf("px") : -1; 403 if (index >= 0 && wStr.slice(0, index) > docWidth) { 404 saveStyleProperty(node, "width", actionLog); 405 saveStyleProperty(node, "minWidth", actionLog); 406 saveStyleProperty(node, "maxWidth", actionLog); 407 node.style.width = "100%"; 408 node.style.minWidth = ""; 409 node.style.maxWidth = wStr; 410 touched = true; 411 } 412 } 413 return touched; 414} 415 416function transformImages(nodes, docWidth, actionLog) { 417 var i, len; 418 var node; 419 var w, h; 420 var touched = false; 421 422 for (i = 0, len = nodes.length; i < len; i++) { 423 node = nodes[i]; 424 w = node.offsetWidth; 425 h = node.offsetHeight; 426 // shrink w/h proportionally if the img is wider than available width 427 if (w > docWidth) { 428 saveStyleProperty(node, "maxWidth", actionLog); 429 saveStyleProperty(node, "width", actionLog); 430 saveStyleProperty(node, "height", actionLog); 431 node.style.maxWidth = docWidth + "px"; 432 node.style.width = "100%"; 433 node.style.height = "auto"; 434 touched = true; 435 } 436 } 437 return touched; 438} 439 440function saveStyleProperty(node, property, actionLog) { 441 var savedName = "data-" + property; 442 node.setAttribute(savedName, node.style[property]); 443 actionLog.push([undoSetProperty, node, [property, savedName]]); 444} 445 446function undoSetProperty(property, savedProperty) { 447 this.style[property] = savedProperty ? this.getAttribute(savedProperty) : ""; 448} 449 450function shouldMungeTable(table) { 451 return table.hasAttribute("width") || table.style.width; 452} 453 454function hideAllUnsafeImages() { 455 hideUnsafeImages(document.getElementsByClassName("mail-message-content")); 456} 457 458function hideUnsafeImages(msgContentDivs) { 459 var i, msgContentCount; 460 var j, imgCount; 461 var msgContentDiv, image; 462 var images; 463 var showImages; 464 var k = 0; 465 var urls = new Array(); 466 var messageIds = new Array(); 467 for (i = 0, msgContentCount = msgContentDivs.length; i < msgContentCount; i++) { 468 msgContentDiv = msgContentDivs[i]; 469 showImages = msgContentDiv.classList.contains("mail-show-images"); 470 471 images = msgContentDiv.getElementsByTagName("img"); 472 for (j = 0, imgCount = images.length; j < imgCount; j++) { 473 image = images[j]; 474 var src = rewriteRelativeImageSrc(image); 475 if (src) { 476 urls[k] = src; 477 messageIds[k] = msgContentDiv.parentNode.id; 478 k++; 479 } 480 attachImageLoadListener(image); 481 // TODO: handle inline image attachments for all supported protocols 482 if (!showImages) { 483 blockImage(image); 484 } 485 } 486 } 487 488 window.mail.onInlineAttachmentsParsed(urls, messageIds); 489} 490 491/** 492 * Changes relative paths to absolute path by pre-pending the account uri. 493 * @param {Element} imgElement Image for which the src path will be updated. 494 * @returns the rewritten image src string or null if the imgElement was not rewritten. 495 */ 496function rewriteRelativeImageSrc(imgElement) { 497 var src = imgElement.src; 498 499 // DOC_BASE_URI will always be a unique x-thread:// uri for this particular conversation 500 if (src.indexOf(DOC_BASE_URI) == 0 && (DOC_BASE_URI != CONVERSATION_BASE_URI)) { 501 // The conversation specifies a different base uri than the document 502 src = CONVERSATION_BASE_URI + src.substring(DOC_BASE_URI.length); 503 imgElement.src = src; 504 return src; 505 } 506 507 // preserve cid urls as is 508 if (src.substring(0, 4) == "cid:") { 509 return src; 510 } 511 512 return null; 513}; 514 515 516function attachImageLoadListener(imageElement) { 517 // Reset the src attribute to the empty string because onload will only fire if the src 518 // attribute is set after the onload listener. 519 var originalSrc = imageElement.src; 520 imageElement.src = ''; 521 imageElement.onload = imageOnLoad; 522 imageElement.src = originalSrc; 523} 524 525/** 526 * Handle an onload event for an <img> tag. 527 * The image could be within an elided-text block, or at the top level of a message. 528 * When a new image loads, its new bounds may affect message or elided-text geometry, 529 * so we need to inspect and adjust the enclosing element's zoom level where necessary. 530 * 531 * Because this method can be called really often, and zoom-level adjustment is slow, 532 * we collect the elements to be processed and do them all later in a single deferred pass. 533 */ 534function imageOnLoad(e) { 535 // normalize the quoted text parent if we're in a quoted text block, or else 536 // normalize the parent message content element 537 var parent = up(e.target, "elided-text") || up(e.target, "mail-message-content"); 538 if (!parent) { 539 // sanity check. shouldn't really happen. 540 return; 541 } 542 543 // if there was no previous work, schedule a new deferred job 544 if (gImageLoadElements.length == 0) { 545 window.setTimeout(handleAllImageOnLoads, 0); 546 } 547 548 // enqueue the work if it wasn't already enqueued 549 if (gImageLoadElements.indexOf(parent) == -1) { 550 gImageLoadElements.push(parent); 551 } 552} 553 554// handle all deferred work from image onload events 555function handleAllImageOnLoads() { 556 normalizeElementWidths(gImageLoadElements); 557 measurePositions(); 558 // clear the queue so the next onload event starts a new job 559 gImageLoadElements = []; 560} 561 562function blockImage(imageElement) { 563 var src = imageElement.src; 564 if (src.indexOf("http://") == 0 || src.indexOf("https://") == 0 || 565 src.indexOf("content://") == 0 || src.indexOf("cid:") == 0) { 566 imageElement.setAttribute(BLOCKED_SRC_ATTR, src); 567 imageElement.src = "data:"; 568 } 569} 570 571function setWideViewport() { 572 var metaViewport = document.getElementById('meta-viewport'); 573 metaViewport.setAttribute('content', 'width=' + WIDE_VIEWPORT_WIDTH); 574} 575 576function restoreScrollPosition() { 577 var scrollYPercent = window.mail.getScrollYPercent(); 578 if (scrollYPercent && document.body.offsetHeight > window.innerHeight) { 579 document.body.scrollTop = Math.floor(scrollYPercent * document.body.offsetHeight); 580 } 581} 582 583function onContentReady(event) { 584 // hack for b/1333356 585 if (RUNNING_KITKAT_OR_LATER) { 586 restoreScrollPosition(); 587 } 588 window.mail.onContentReady(); 589} 590 591function setupContentReady() { 592 var signalDiv; 593 594 // PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER 595 // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op 596 // animation that immediately runs on page load. The app uses this as a signal that the 597 // content is loaded and ready to draw, since WebView delays firing this event until the 598 // layers are composited and everything is ready to draw. 599 // 600 // This code is conditionally enabled on JB+ by setting the ENABLE_CONTENT_READY flag. 601 if (ENABLE_CONTENT_READY) { 602 signalDiv = document.getElementById("initial-load-signal"); 603 signalDiv.addEventListener("webkitAnimationStart", onContentReady, false); 604 } 605} 606 607// BEGIN Java->JavaScript handlers 608function measurePositions() { 609 var overlayTops, overlayBottoms; 610 var i; 611 var len; 612 613 var expandedBody, headerSpacer; 614 var prevBodyBottom = 0; 615 var expandedBodyDivs = document.querySelectorAll(".expanded > .mail-message-content"); 616 617 // N.B. offsetTop and offsetHeight of an element with the "zoom:" style applied cannot be 618 // trusted. 619 620 overlayTops = new Array(expandedBodyDivs.length + 1); 621 overlayBottoms = new Array(expandedBodyDivs.length + 1); 622 for (i = 0, len = expandedBodyDivs.length; i < len; i++) { 623 expandedBody = expandedBodyDivs[i]; 624 headerSpacer = expandedBody.previousElementSibling; 625 // addJavascriptInterface handler only supports string arrays 626 overlayTops[i] = prevBodyBottom; 627 overlayBottoms[i] = (getTotalOffset(headerSpacer).top + headerSpacer.offsetHeight); 628 prevBodyBottom = getTotalOffset(expandedBody.nextElementSibling).top; 629 } 630 // add an extra one to mark the top/bottom of the last message footer spacer 631 overlayTops[i] = prevBodyBottom; 632 overlayBottoms[i] = document.documentElement.scrollHeight; 633 634 window.mail.onWebContentGeometryChange(overlayTops, overlayBottoms); 635} 636 637function unblockImages(messageDomIds) { 638 var i, j, images, imgCount, image, blockedSrc; 639 for (j = 0, len = messageDomIds.length; j < len; j++) { 640 var messageDomId = messageDomIds[j]; 641 var msg = document.getElementById(messageDomId); 642 if (!msg) { 643 console.log("can't unblock, no matching message for id: " + messageDomId); 644 continue; 645 } 646 images = msg.getElementsByTagName("img"); 647 for (i = 0, imgCount = images.length; i < imgCount; i++) { 648 image = images[i]; 649 blockedSrc = image.getAttribute(BLOCKED_SRC_ATTR); 650 if (blockedSrc) { 651 image.src = blockedSrc; 652 image.removeAttribute(BLOCKED_SRC_ATTR); 653 } 654 } 655 } 656} 657 658function setConversationHeaderSpacerHeight(spacerHeight) { 659 var spacer = document.getElementById("conversation-header"); 660 if (!spacer) { 661 console.log("can't set spacer for conversation header"); 662 return; 663 } 664 spacer.style.height = spacerHeight + "px"; 665 measurePositions(); 666} 667 668function setConversationFooterSpacerHeight(spacerHeight) { 669 var spacer = document.getElementById("conversation-footer"); 670 if (!spacer) { 671 console.log("can't set spacer for conversation footer"); 672 return; 673 } 674 spacer.style.height = spacerHeight + "px"; 675 measurePositions(); 676} 677 678function setMessageHeaderSpacerHeight(messageDomId, spacerHeight) { 679 var spacer = document.querySelector("#" + messageDomId + " > .mail-message-header"); 680 setSpacerHeight(spacer, spacerHeight); 681} 682 683function setSpacerHeight(spacer, spacerHeight) { 684 if (!spacer) { 685 console.log("can't set spacer for message with id: " + messageDomId); 686 return; 687 } 688 spacer.style.height = spacerHeight + "px"; 689 measurePositions(); 690} 691 692function setMessageBodyVisible(messageDomId, isVisible, spacerHeight) { 693 var i, len; 694 var visibility = isVisible ? "block" : "none"; 695 var messageDiv = document.querySelector("#" + messageDomId); 696 var collapsibleDivs = document.querySelectorAll("#" + messageDomId + " > .collapsible"); 697 if (!messageDiv || collapsibleDivs.length == 0) { 698 console.log("can't set body visibility for message with id: " + messageDomId); 699 return; 700 } 701 702 messageDiv.classList.toggle("expanded"); 703 for (i = 0, len = collapsibleDivs.length; i < len; i++) { 704 collapsibleDivs[i].style.display = visibility; 705 } 706 707 // revealing new content should trigger width normalization, since the initial render 708 // skips collapsed and super-collapsed messages 709 if (isVisible) { 710 normalizeElementWidths(messageDiv.getElementsByClassName("mail-message-content")); 711 } 712 713 setMessageHeaderSpacerHeight(messageDomId, spacerHeight); 714} 715 716function replaceSuperCollapsedBlock(startIndex) { 717 var parent, block, msg; 718 719 block = document.querySelector(".mail-super-collapsed-block[index='" + startIndex + "']"); 720 if (!block) { 721 console.log("can't expand super collapsed block at index: " + startIndex); 722 return; 723 } 724 parent = block.parentNode; 725 block.innerHTML = window.mail.getTempMessageBodies(); 726 727 // process the new block contents in one go before we pluck them out of the common ancestor 728 processQuotedText(block, false /* showElided */); 729 hideUnsafeImages(block.getElementsByClassName("mail-message-content")); 730 731 msg = block.firstChild; 732 while (msg) { 733 parent.insertBefore(msg, block); 734 msg = block.firstChild; 735 } 736 parent.removeChild(block); 737 disablePostForms(); 738 measurePositions(); 739} 740 741function processNewMessageBody(msgContentDiv) { 742 processQuotedText(msgContentDiv, true /* showElided */); 743 hideUnsafeImages([msgContentDiv]); 744 if (up(msgContentDiv, "mail-message").classList.contains("expanded")) { 745 normalizeElementWidths([msgContentDiv]); 746 } 747} 748 749function replaceMessageBodies(messageIds) { 750 var i; 751 var id; 752 var msgContentDiv; 753 754 for (i = 0, len = messageIds.length; i < len; i++) { 755 id = messageIds[i]; 756 msgContentDiv = document.querySelector("#" + id + " > .mail-message-content"); 757 // Check if we actually have a div before trying to replace this message body. 758 if (msgContentDiv) { 759 msgContentDiv.innerHTML = window.mail.getMessageBody(id); 760 processNewMessageBody(msgContentDiv); 761 } else { 762 // There's no message div, just skip it. We're in a really busted state. 763 console.log("Mail message content for msg " + id + " to replace not found."); 764 } 765 } 766 disablePostForms(); 767 measurePositions(); 768} 769 770// handle the special case of adding a single new message at the end of a conversation 771function appendMessageHtml() { 772 var msg = document.createElement("div"); 773 msg.innerHTML = window.mail.getTempMessageBodies(); 774 var body = msg.children[0]; // toss the outer div, it was just to render innerHTML into 775 document.body.insertBefore(body, document.getElementById("conversation-footer")); 776 processNewMessageBody(body.querySelector(".mail-message-content")); 777 disablePostForms(); 778 measurePositions(); 779} 780 781function disablePostForms() { 782 var forms = document.getElementsByTagName('FORM'); 783 var i; 784 var j; 785 var elements; 786 787 for (i = 0; i < forms.length; ++i) { 788 if (forms[i].method.toUpperCase() === 'POST') { 789 forms[i].onsubmit = function() { 790 alert(MSG_FORMS_ARE_DISABLED); 791 return false; 792 } 793 elements = forms[i].elements; 794 for (j = 0; j < elements.length; ++j) { 795 if (elements[j].type != 'submit') { 796 elements[j].disabled = true; 797 } 798 } 799 } 800 } 801} 802// END Java->JavaScript handlers 803 804// Do this first to ensure that the readiness signal comes through, 805// even if a stray exception later occurs. 806setupContentReady(); 807 808collapseAllQuotedText(); 809hideAllUnsafeImages(); 810normalizeAllMessageWidths(); 811//setWideViewport(); 812// hack for b/1333356 813if (!RUNNING_KITKAT_OR_LATER) { 814 restoreScrollPosition(); 815} 816disablePostForms(); 817measurePositions(); 818