seamlessfield.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Class to encapsulate an editable field that blends in with
  16. * the style of the page. The field can be fixed height, grow with its
  17. * contents, or have a min height after which it grows to its contents.
  18. * This is a goog.editor.Field, but with blending and sizing capabilities,
  19. * and avoids using an iframe whenever possible.
  20. *
  21. * @author nicksantos@google.com (Nick Santos)
  22. * @see ../demos/editor/seamlessfield.html
  23. */
  24. goog.provide('goog.editor.SeamlessField');
  25. goog.require('goog.cssom.iframe.style');
  26. goog.require('goog.dom');
  27. goog.require('goog.dom.Range');
  28. goog.require('goog.dom.TagName');
  29. goog.require('goog.dom.safe');
  30. goog.require('goog.editor.BrowserFeature');
  31. goog.require('goog.editor.Field');
  32. goog.require('goog.editor.icontent');
  33. goog.require('goog.editor.icontent.FieldFormatInfo');
  34. goog.require('goog.editor.icontent.FieldStyleInfo');
  35. goog.require('goog.editor.node');
  36. goog.require('goog.events');
  37. goog.require('goog.events.EventType');
  38. goog.require('goog.html.legacyconversions');
  39. goog.require('goog.html.uncheckedconversions');
  40. goog.require('goog.log');
  41. goog.require('goog.string.Const');
  42. goog.require('goog.style');
  43. /**
  44. * This class encapsulates an editable field that blends in with the
  45. * surrounding page.
  46. * To see events fired by this object, please see the base class.
  47. *
  48. * @param {string} id An identifer for the field. This is used to find the
  49. * field and the element associated with this field.
  50. * @param {Document=} opt_doc The document that the element with the given
  51. * id can be found it.
  52. * @constructor
  53. * @extends {goog.editor.Field}
  54. */
  55. goog.editor.SeamlessField = function(id, opt_doc) {
  56. goog.editor.Field.call(this, id, opt_doc);
  57. };
  58. goog.inherits(goog.editor.SeamlessField, goog.editor.Field);
  59. /**
  60. * @override
  61. */
  62. goog.editor.SeamlessField.prototype.logger =
  63. goog.log.getLogger('goog.editor.SeamlessField');
  64. // Functions dealing with field sizing.
  65. /**
  66. * The key used for listening for the "dragover" event.
  67. * @type {goog.events.Key}
  68. * @private
  69. */
  70. goog.editor.SeamlessField.prototype.listenForDragOverEventKey_;
  71. /**
  72. * The key used for listening for the iframe "load" event.
  73. * @type {goog.events.Key}
  74. * @private
  75. */
  76. goog.editor.SeamlessField.prototype.listenForIframeLoadEventKey_;
  77. /**
  78. * Sets the min height of this editable field's iframe. Only used in growing
  79. * mode when an iframe is used. This will cause an immediate field sizing to
  80. * update the field if necessary based on the new min height.
  81. * @param {number} height The min height specified as a number of pixels,
  82. * e.g., 75.
  83. */
  84. goog.editor.SeamlessField.prototype.setMinHeight = function(height) {
  85. if (height == this.minHeight_) {
  86. // Do nothing if the min height isn't changing.
  87. return;
  88. }
  89. this.minHeight_ = height;
  90. if (this.usesIframe()) {
  91. this.doFieldSizingGecko();
  92. }
  93. };
  94. /**
  95. * Whether the field should be rendered with a fixed height, or should expand
  96. * to fit its contents.
  97. * @type {boolean}
  98. * @private
  99. */
  100. goog.editor.SeamlessField.prototype.isFixedHeight_ = false;
  101. /**
  102. * Whether the fixed-height handling has been overridden manually.
  103. * @type {boolean}
  104. * @private
  105. */
  106. goog.editor.SeamlessField.prototype.isFixedHeightOverridden_ = false;
  107. /**
  108. * @return {boolean} Whether the field should be rendered with a fixed
  109. * height, or should expand to fit its contents.
  110. * @override
  111. */
  112. goog.editor.SeamlessField.prototype.isFixedHeight = function() {
  113. return this.isFixedHeight_;
  114. };
  115. /**
  116. * @param {boolean} newVal Explicitly set whether the field should be
  117. * of a fixed-height. This overrides auto-detection.
  118. */
  119. goog.editor.SeamlessField.prototype.overrideFixedHeight = function(newVal) {
  120. this.isFixedHeight_ = newVal;
  121. this.isFixedHeightOverridden_ = true;
  122. };
  123. /**
  124. * Auto-detect whether the current field should have a fixed height.
  125. * @private
  126. */
  127. goog.editor.SeamlessField.prototype.autoDetectFixedHeight_ = function() {
  128. if (!this.isFixedHeightOverridden_) {
  129. var originalElement = this.getOriginalElement();
  130. if (originalElement) {
  131. this.isFixedHeight_ =
  132. goog.style.getComputedOverflowY(originalElement) == 'auto';
  133. }
  134. }
  135. };
  136. /**
  137. * Resize the iframe in response to the wrapper div changing size.
  138. * @private
  139. */
  140. goog.editor.SeamlessField.prototype.handleOuterDocChange_ = function() {
  141. if (this.isEventStopped(goog.editor.Field.EventType.CHANGE)) {
  142. return;
  143. }
  144. this.sizeIframeToWrapperGecko_();
  145. };
  146. /**
  147. * Sizes the iframe to its body's height.
  148. * @private
  149. */
  150. goog.editor.SeamlessField.prototype.sizeIframeToBodyHeightGecko_ = function() {
  151. if (this.acquireSizeIframeLockGecko_()) {
  152. var resized = false;
  153. var ifr = this.getEditableIframe();
  154. if (ifr) {
  155. var fieldHeight = this.getIframeBodyHeightGecko_();
  156. if (this.minHeight_) {
  157. fieldHeight = Math.max(fieldHeight, this.minHeight_);
  158. }
  159. if (parseInt(goog.style.getStyle(ifr, 'height'), 10) != fieldHeight) {
  160. ifr.style.height = fieldHeight + 'px';
  161. resized = true;
  162. }
  163. }
  164. this.releaseSizeIframeLockGecko_();
  165. if (resized) {
  166. this.dispatchEvent(goog.editor.Field.EventType.IFRAME_RESIZED);
  167. }
  168. }
  169. };
  170. /**
  171. * @return {number} The height of the editable iframe's body.
  172. * @private
  173. */
  174. goog.editor.SeamlessField.prototype.getIframeBodyHeightGecko_ = function() {
  175. var ifr = this.getEditableIframe();
  176. var body = ifr.contentDocument.body;
  177. var htmlElement = /** @type {!HTMLElement} */ (body.parentNode);
  178. // If the iframe's height is 0, then the offsetHeight/scrollHeight of the
  179. // HTML element in the iframe can be totally wack (i.e. too large
  180. // by 50-500px). Also, in standard's mode the clientHeight is 0.
  181. if (parseInt(goog.style.getStyle(ifr, 'height'), 10) === 0) {
  182. goog.style.setStyle(ifr, 'height', 1 + 'px');
  183. }
  184. var fieldHeight;
  185. if (goog.editor.node.isStandardsMode(body)) {
  186. // If in standards-mode,
  187. // grab the HTML element as it will contain all the field's
  188. // contents. The body's height, for example, will not include that of
  189. // floated images at the bottom in standards mode.
  190. // Note that this value include all scrollbars *except* for scrollbars
  191. // on the HTML element itself.
  192. fieldHeight = htmlElement.offsetHeight;
  193. } else {
  194. // In quirks-mode, the body-element always seems
  195. // to size to the containing window. The html-element however,
  196. // sizes to the content, and can thus end up with a value smaller
  197. // than its child body-element if the content is shrinking.
  198. // We want to make the iframe shrink too when the content shrinks,
  199. // so rather than size the iframe to the body-element, size it to
  200. // the html-element.
  201. fieldHeight = htmlElement.scrollHeight;
  202. // If there is a horizontal scroll, add in the thickness of the
  203. // scrollbar.
  204. if (htmlElement.clientHeight != htmlElement.offsetHeight) {
  205. fieldHeight += goog.editor.SeamlessField.getScrollbarWidth_();
  206. }
  207. }
  208. return fieldHeight;
  209. };
  210. /**
  211. * Grabs the width of a scrollbar from the browser and caches the result.
  212. * @return {number} The scrollbar width in pixels.
  213. * @private
  214. */
  215. goog.editor.SeamlessField.getScrollbarWidth_ = function() {
  216. return goog.editor.SeamlessField.scrollbarWidth_ ||
  217. (goog.editor.SeamlessField.scrollbarWidth_ =
  218. goog.style.getScrollbarWidth());
  219. };
  220. /**
  221. * Sizes the iframe to its container div's width. The width of the div
  222. * is controlled by its containing context, not by its contents.
  223. * if it extends outside of it's contents, then it gets a horizontal scroll.
  224. * @private
  225. */
  226. goog.editor.SeamlessField.prototype.sizeIframeToWrapperGecko_ = function() {
  227. if (this.acquireSizeIframeLockGecko_()) {
  228. var ifr = this.getEditableIframe();
  229. var field = this.getElement();
  230. var resized = false;
  231. if (ifr && field) {
  232. var fieldPaddingBox;
  233. var widthDiv = /** @type {!HTMLElement} */ (ifr.parentNode);
  234. var width = widthDiv.offsetWidth;
  235. if (parseInt(goog.style.getStyle(ifr, 'width'), 10) != width) {
  236. fieldPaddingBox = goog.style.getPaddingBox(field);
  237. ifr.style.width = width + 'px';
  238. field.style.width =
  239. width - fieldPaddingBox.left - fieldPaddingBox.right + 'px';
  240. resized = true;
  241. }
  242. var height = widthDiv.offsetHeight;
  243. if (this.isFixedHeight() &&
  244. parseInt(goog.style.getStyle(ifr, 'height'), 10) != height) {
  245. if (!fieldPaddingBox) {
  246. fieldPaddingBox = goog.style.getPaddingBox(field);
  247. }
  248. ifr.style.height = height + 'px';
  249. field.style.height =
  250. height - fieldPaddingBox.top - fieldPaddingBox.bottom + 'px';
  251. resized = true;
  252. }
  253. }
  254. this.releaseSizeIframeLockGecko_();
  255. if (resized) {
  256. this.dispatchEvent(goog.editor.Field.EventType.IFRAME_RESIZED);
  257. }
  258. }
  259. };
  260. /**
  261. * Perform all the sizing immediately.
  262. */
  263. goog.editor.SeamlessField.prototype.doFieldSizingGecko = function() {
  264. // Because doFieldSizingGecko can be called after a setTimeout
  265. // it is possible that the field has been destroyed before this call
  266. // to do the sizing is executed. Check for field existence and do nothing
  267. // if it has already been destroyed.
  268. if (this.getElement()) {
  269. // The order of operations is important here. Sizing the iframe to the
  270. // wrapper could cause the width to change, which could change the line
  271. // wrapping, which could change the body height. So we need to do that
  272. // first, then size the iframe to fit the body height.
  273. this.sizeIframeToWrapperGecko_();
  274. if (!this.isFixedHeight()) {
  275. this.sizeIframeToBodyHeightGecko_();
  276. }
  277. }
  278. };
  279. /**
  280. * Acquires a lock on resizing the field iframe. This is used to ensure that
  281. * modifications we make while in a mutation event handler don't cause
  282. * infinite loops.
  283. * @return {boolean} False if the lock is already acquired.
  284. * @private
  285. */
  286. goog.editor.SeamlessField.prototype.acquireSizeIframeLockGecko_ = function() {
  287. if (this.sizeIframeLock_) {
  288. return false;
  289. }
  290. return this.sizeIframeLock_ = true;
  291. };
  292. /**
  293. * Releases a lock on resizing the field iframe. This is used to ensure that
  294. * modifications we make while in a mutation event handler don't cause
  295. * infinite loops.
  296. * @private
  297. */
  298. goog.editor.SeamlessField.prototype.releaseSizeIframeLockGecko_ = function() {
  299. this.sizeIframeLock_ = false;
  300. };
  301. // Functions dealing with blending in with the surrounding page.
  302. /**
  303. * String containing the css rules that, if applied to a document's body,
  304. * would style that body as if it were the original element we made editable.
  305. * See goog.cssom.iframe.style.getElementContext for more details.
  306. * @type {string}
  307. * @private
  308. */
  309. goog.editor.SeamlessField.prototype.iframeableCss_ = '';
  310. /**
  311. * Gets the css rules that should be used to style an iframe's body as if it
  312. * were the original element that we made editable.
  313. * @param {boolean=} opt_forceRegeneration Set to true to not read the cached
  314. * copy and instead completely regenerate the css rules.
  315. * @return {string} The string containing the css rules to use.
  316. */
  317. goog.editor.SeamlessField.prototype.getIframeableCss = function(
  318. opt_forceRegeneration) {
  319. if (!this.iframeableCss_ || opt_forceRegeneration) {
  320. var originalElement = this.getOriginalElement();
  321. if (originalElement) {
  322. this.iframeableCss_ = goog.cssom.iframe.style.getElementContext(
  323. originalElement, opt_forceRegeneration);
  324. }
  325. }
  326. return this.iframeableCss_;
  327. };
  328. /**
  329. * Sets the css rules that should be used inside the editable iframe.
  330. * Note: to clear the css cache between makeNotEditable/makeEditable,
  331. * call this with "" as iframeableCss.
  332. * TODO(user): Unify all these css setting methods + Nick's open
  333. * CL. This is getting ridiculous.
  334. * @param {string} iframeableCss String containing the css rules to use.
  335. */
  336. goog.editor.SeamlessField.prototype.setIframeableCss = function(iframeableCss) {
  337. this.iframeableCss_ = iframeableCss;
  338. };
  339. /**
  340. * Used to ensure that CSS stylings are only installed once for none
  341. * iframe seamless mode.
  342. * TODO(user): Make it a formal part of the API that you can only
  343. * set one set of styles globally.
  344. * In seamless, non-iframe mode, all the stylings would go in the
  345. * same document and conflict.
  346. * @type {boolean}
  347. * @private
  348. */
  349. goog.editor.SeamlessField.haveInstalledCss_ = false;
  350. /**
  351. * Applies CSS from the wrapper-div to the field iframe.
  352. */
  353. goog.editor.SeamlessField.prototype.inheritBlendedCSS = function() {
  354. // No-op if the field isn't using an iframe.
  355. if (!this.usesIframe()) {
  356. return;
  357. }
  358. var field = this.getElement();
  359. var head = goog.dom.getDomHelper(field).getElementsByTagNameAndClass(
  360. goog.dom.TagName.HEAD)[0];
  361. if (head) {
  362. // We created this <head>, and we know the only thing we put in there
  363. // is a <style> block. So it's safe to blow away all the children
  364. // as part of rewriting the styles.
  365. goog.dom.removeChildren(head);
  366. }
  367. // Force a cache-clearing in CssUtil - this function was called because
  368. // we're applying the 'blend' for the first time, or because we
  369. // *need* to recompute the blend.
  370. var newCSS = this.getIframeableCss(true);
  371. goog.style.installSafeStyleSheet(
  372. goog.html.legacyconversions.safeStyleSheetFromString(newCSS), field);
  373. };
  374. // Overridden methods.
  375. /** @override */
  376. goog.editor.SeamlessField.prototype.usesIframe = function() {
  377. // TODO(user): Switch Firefox to using contentEditable
  378. // rather than designMode iframe once contentEditable support
  379. // is less buggy.
  380. return !goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE;
  381. };
  382. /** @override */
  383. goog.editor.SeamlessField.prototype.setupMutationEventHandlersGecko =
  384. function() {
  385. goog.editor.SeamlessField.superClass_.setupMutationEventHandlersGecko.call(
  386. this);
  387. if (this.usesIframe()) {
  388. var iframe = this.getEditableIframe();
  389. var outerDoc = iframe.ownerDocument;
  390. this.eventRegister.listen(
  391. outerDoc, goog.editor.Field.MUTATION_EVENTS_GECKO,
  392. this.handleOuterDocChange_, true);
  393. // If the images load after we do the initial sizing, then this will
  394. // force a field resize.
  395. this.listenForIframeLoadEventKey_ = goog.events.listenOnce(
  396. this.getEditableDomHelper().getWindow(), goog.events.EventType.LOAD,
  397. this.sizeIframeToBodyHeightGecko_, true, this);
  398. this.eventRegister.listen(
  399. outerDoc, 'DOMAttrModified',
  400. goog.bind(this.handleDomAttrChange, this, this.handleOuterDocChange_),
  401. true);
  402. }
  403. };
  404. /** @override */
  405. goog.editor.SeamlessField.prototype.handleChange = function() {
  406. if (this.isEventStopped(goog.editor.Field.EventType.CHANGE)) {
  407. return;
  408. }
  409. goog.editor.SeamlessField.superClass_.handleChange.call(this);
  410. if (this.usesIframe()) {
  411. this.sizeIframeToBodyHeightGecko_();
  412. }
  413. };
  414. /** @override */
  415. goog.editor.SeamlessField.prototype.dispatchBlur = function() {
  416. if (this.isEventStopped(goog.editor.Field.EventType.BLUR)) {
  417. return;
  418. }
  419. goog.editor.SeamlessField.superClass_.dispatchBlur.call(this);
  420. // Clear the selection and restore the current range back after collapsing
  421. // it. The ideal solution would have been to just leave the range intact; but
  422. // when there are multiple fields present on the page, its important that
  423. // the selection isn't retained when we switch between the fields. We also
  424. // have to make sure that the cursor position is retained when we tab in and
  425. // out of a field and our approach addresses both these issues.
  426. // Another point to note is that we do it on a setTimeout to allow for
  427. // DOM modifications on blur. Otherwise, something like setLoremIpsum will
  428. // leave a blinking cursor in the field even though it's blurred.
  429. if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE &&
  430. !goog.editor.BrowserFeature.CLEARS_SELECTION_WHEN_FOCUS_LEAVES) {
  431. var win = this.getEditableDomHelper().getWindow();
  432. var dragging = false;
  433. goog.events.unlistenByKey(this.listenForDragOverEventKey_);
  434. this.listenForDragOverEventKey_ = goog.events.listenOnce(
  435. win.document.body, 'dragover', function() { dragging = true; });
  436. goog.global.setTimeout(goog.bind(function() {
  437. // Do not clear the selection if we're only dragging text.
  438. // This addresses a bug on FF1.5/linux where dragging fires a blur,
  439. // but clearing the selection confuses Firefox's drag-and-drop
  440. // implementation. For more info, see http://b/1061064
  441. if (!dragging) {
  442. if (this.editableDomHelper) {
  443. var rng = this.getRange();
  444. // If there are multiple fields on a page, we need to make sure that
  445. // the selection isn't retained when we switch between fields. We
  446. // could have collapsed the range but there is a bug in GECKO where
  447. // the selection stays highlighted even though its backing range is
  448. // collapsed (http://b/1390115). To get around this, we clear the
  449. // selection and restore the collapsed range back in. Restoring the
  450. // range is important so that the cursor stays intact when we tab out
  451. // and into a field (See http://b/1790301 for additional details on
  452. // this).
  453. var iframeWindow = this.editableDomHelper.getWindow();
  454. goog.dom.Range.clearSelection(iframeWindow);
  455. if (rng) {
  456. rng.collapse(true);
  457. rng.select();
  458. }
  459. }
  460. }
  461. }, this), 0);
  462. }
  463. };
  464. /** @override */
  465. goog.editor.SeamlessField.prototype.turnOnDesignModeGecko = function() {
  466. goog.editor.SeamlessField.superClass_.turnOnDesignModeGecko.call(this);
  467. var doc = this.getEditableDomHelper().getDocument();
  468. doc.execCommand('enableInlineTableEditing', false, 'false');
  469. doc.execCommand('enableObjectResizing', false, 'false');
  470. };
  471. /** @override */
  472. goog.editor.SeamlessField.prototype.installStyles = function() {
  473. if (!this.usesIframe()) {
  474. if (!goog.editor.SeamlessField.haveInstalledCss_) {
  475. if (this.cssStyles) {
  476. goog.style.installSafeStyleSheet(
  477. goog.html.legacyconversions.safeStyleSheetFromString(
  478. this.cssStyles),
  479. this.getElement());
  480. }
  481. // TODO(user): this should be reset to false when the editor is quit.
  482. // In non-iframe mode, CSS styles should only be instaled once.
  483. goog.editor.SeamlessField.haveInstalledCss_ = true;
  484. }
  485. }
  486. };
  487. /** @override */
  488. goog.editor.SeamlessField.prototype.makeEditableInternal = function(
  489. opt_iframeSrc) {
  490. if (this.usesIframe()) {
  491. goog.editor.SeamlessField.superClass_.makeEditableInternal.call(
  492. this, opt_iframeSrc);
  493. } else {
  494. var field = this.getOriginalElement();
  495. if (field) {
  496. this.setupFieldObject(field);
  497. field.contentEditable = true;
  498. this.injectContents(field.innerHTML, field);
  499. this.handleFieldLoad();
  500. }
  501. }
  502. };
  503. /** @override */
  504. goog.editor.SeamlessField.prototype.handleFieldLoad = function() {
  505. if (this.usesIframe()) {
  506. // If the CSS inheriting code screws up (e.g. makes fonts too large) and
  507. // the field is sized off in goog.editor.Field.makeIframeField, then we need
  508. // to size it correctly, but it needs to be visible for the browser
  509. // to have fully rendered it. We need to put this on a timeout to give
  510. // the browser time to render.
  511. var self = this;
  512. goog.global.setTimeout(function() { self.doFieldSizingGecko(); }, 0);
  513. }
  514. goog.editor.SeamlessField.superClass_.handleFieldLoad.call(this);
  515. };
  516. /** @override */
  517. goog.editor.SeamlessField.prototype.getIframeAttributes = function() {
  518. return {'frameBorder': 0, 'style': 'padding:0;'};
  519. };
  520. /** @override */
  521. goog.editor.SeamlessField.prototype.attachIframe = function(iframe) {
  522. this.autoDetectFixedHeight_();
  523. var field = this.getOriginalElement();
  524. var dh = goog.dom.getDomHelper(field);
  525. // Grab the width/height values of the field before modifying any CSS
  526. // as some of the modifications affect its size (e.g. innerHTML='')
  527. // Here, we set the size of the field to fixed so there's not too much
  528. // jiggling when we set the innerHTML of the field.
  529. var oldWidth = field.style.width;
  530. var oldHeight = field.style.height;
  531. goog.style.setStyle(field, 'visibility', 'hidden');
  532. // If there is a floated element at the bottom of the field,
  533. // then it needs a clearing div at the end to cause the clientHeight
  534. // to contain the entire field.
  535. // Also, with css re-writing, the margins of the first/last
  536. // paragraph don't seem to get included in the clientHeight. Specifically,
  537. // the extra divs below force the field's clientHeight to include the
  538. // margins on the first and last elements contained within it.
  539. var startDiv = dh.createDom(
  540. goog.dom.TagName.DIV,
  541. {'style': 'height:0;clear:both', 'innerHTML': '&nbsp;'});
  542. var endDiv = startDiv.cloneNode(true);
  543. field.insertBefore(startDiv, field.firstChild);
  544. goog.dom.appendChild(field, endDiv);
  545. var contentBox = goog.style.getContentBoxSize(field);
  546. var width = contentBox.width;
  547. var height = contentBox.height;
  548. var html = '';
  549. if (this.isFixedHeight()) {
  550. html = '&nbsp;';
  551. goog.style.setStyle(field, 'position', 'relative');
  552. goog.style.setStyle(field, 'overflow', 'visible');
  553. goog.style.setStyle(iframe, 'position', 'absolute');
  554. goog.style.setStyle(iframe, 'top', '0');
  555. goog.style.setStyle(iframe, 'left', '0');
  556. }
  557. goog.style.setSize(field, width, height);
  558. // In strict mode, browsers put blank space at the bottom and right
  559. // if a field when it has an iframe child, to fill up the remaining line
  560. // height. So make the line height = 0.
  561. if (goog.editor.node.isStandardsMode(field)) {
  562. this.originalFieldLineHeight_ = field.style.lineHeight;
  563. goog.style.setStyle(field, 'lineHeight', '0');
  564. }
  565. goog.editor.node.replaceInnerHtml(field, html);
  566. // Set the initial size
  567. goog.style.setSize(iframe, width, height);
  568. goog.style.setSize(field, oldWidth, oldHeight);
  569. goog.style.setStyle(field, 'visibility', '');
  570. goog.dom.appendChild(field, iframe);
  571. // Only write if its not IE HTTPS in which case we're waiting for load.
  572. if (!this.shouldLoadAsynchronously()) {
  573. var doc = iframe.contentWindow.document;
  574. if (goog.editor.node.isStandardsMode(iframe.ownerDocument)) {
  575. doc.open();
  576. var emptyHtml =
  577. goog.html.uncheckedconversions
  578. .safeHtmlFromStringKnownToSatisfyTypeContract(
  579. goog.string.Const.from('HTML from constant string'),
  580. '<!DOCTYPE HTML><html></html>');
  581. goog.dom.safe.documentWrite(doc, emptyHtml);
  582. doc.close();
  583. }
  584. }
  585. };
  586. /** @override */
  587. goog.editor.SeamlessField.prototype.getFieldFormatInfo = function(extraStyles) {
  588. var originalElement = this.getOriginalElement();
  589. if (originalElement) {
  590. return new goog.editor.icontent.FieldFormatInfo(
  591. this.id, goog.editor.node.isStandardsMode(originalElement), true,
  592. this.isFixedHeight(), extraStyles);
  593. }
  594. throw Error('no field');
  595. };
  596. /** @override */
  597. goog.editor.SeamlessField.prototype.writeIframeContent = function(
  598. iframe, innerHtml, extraStyles) {
  599. // For seamless iframes, hide the iframe while we're laying it out to
  600. // prevent the flicker.
  601. goog.style.setStyle(iframe, 'visibility', 'hidden');
  602. var formatInfo = this.getFieldFormatInfo(extraStyles);
  603. var styleInfo = new goog.editor.icontent.FieldStyleInfo(
  604. this.getOriginalElement(), this.cssStyles + this.getIframeableCss());
  605. goog.editor.icontent.writeNormalInitialBlendedIframe(
  606. formatInfo, innerHtml, styleInfo, iframe);
  607. this.doFieldSizingGecko();
  608. goog.style.setStyle(iframe, 'visibility', 'visible');
  609. };
  610. /** @override */
  611. goog.editor.SeamlessField.prototype.restoreDom = function() {
  612. // TODO(user): Consider only removing the iframe if we are
  613. // restoring the original node.
  614. if (this.usesIframe()) {
  615. goog.dom.removeNode(this.getEditableIframe());
  616. }
  617. };
  618. /** @override */
  619. goog.editor.SeamlessField.prototype.clearListeners = function() {
  620. goog.events.unlistenByKey(this.listenForDragOverEventKey_);
  621. goog.events.unlistenByKey(this.listenForIframeLoadEventKey_);
  622. goog.editor.SeamlessField.base(this, 'clearListeners');
  623. };