No Description

utils.js 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. 'use strict';
  2. import {Dimensions} from 'react-native';
  3. const domMutationObserveScript = `
  4. var MutationObserver =
  5. window.MutationObserver || window.WebKitMutationObserver;
  6. var observer = new MutationObserver(updateSize);
  7. observer.observe(document, {
  8. subtree: true,
  9. attributes: true
  10. });
  11. `;
  12. const updateSizeWithMessage = (element, scalesPageToFit) =>
  13. `
  14. var usingScale = ${scalesPageToFit} ? screen.width / window.innerWidth : 1;
  15. var scaling = false;
  16. var zoomedin = false;
  17. var lastHeight = 0;
  18. var heightTheSameTimes = 0;
  19. var maxHeightTheSameTimes = 5;
  20. var forceRefreshDelay = 1000;
  21. var forceRefreshTimeout;
  22. var checkPostMessageTimeout;
  23. function updateSize() {
  24. if (zoomedin || scaling || document.fullscreenElement) {
  25. return;
  26. }
  27. if (
  28. !window.hasOwnProperty('ReactNativeWebView') ||
  29. !window.ReactNativeWebView.hasOwnProperty('postMessage')
  30. ) {
  31. checkPostMessageTimeout = setTimeout(updateSize, 200);
  32. return;
  33. }
  34. clearTimeout(checkPostMessageTimeout);
  35. height = ${element}.offsetHeight || document.documentElement.offsetHeight;
  36. width = ${element}.offsetWidth || document.documentElement.offsetWidth;
  37. window.ReactNativeWebView.postMessage(JSON.stringify({ width: Math.min(width, screen.width), height: height * usingScale }));
  38. // Make additional height checks (required to fix issues wit twitter embeds)
  39. clearTimeout(forceRefreshTimeout);
  40. if (lastHeight !== height) {
  41. heightTheSameTimes = 1;
  42. } else {
  43. heightTheSameTimes++;
  44. }
  45. lastHeight = height;
  46. if (heightTheSameTimes <= maxHeightTheSameTimes) {
  47. forceRefreshTimeout = setTimeout(
  48. updateSize,
  49. heightTheSameTimes * forceRefreshDelay
  50. );
  51. }
  52. }
  53. `;
  54. const setViewportContent = (content) => {
  55. if (!content) {
  56. return '';
  57. }
  58. return `
  59. var meta = document.createElement("meta");
  60. meta.setAttribute("name", "viewport");
  61. meta.setAttribute("content", "${content}");
  62. document.getElementsByTagName("head")[0].appendChild(meta);
  63. `;
  64. };
  65. const detectZoomChanged = `
  66. var latestTapStamp = 0;
  67. var lastScale = 1.0;
  68. var doubleTapDelay = 400;
  69. function detectZoomChanged() {
  70. var tempZoomedin = (screen.width / window.innerWidth) > usingScale;
  71. tempZoomedin !== zoomedin && window.ReactNativeWebView.postMessage(JSON.stringify({ zoomedin: tempZoomedin }));
  72. zoomedin = tempZoomedin;
  73. }
  74. window.addEventListener('ontouchstart', event => {
  75. if (event.touches.length === 2) {
  76. scaling = true;
  77. }
  78. })
  79. window.addEventListener('touchend', event => {
  80. if(scaling) {
  81. scaleing = false;
  82. }
  83. var tempScale = event.scale;
  84. tempScale !== lastScale && detectZoomChanged();
  85. lastScale = tempScale;
  86. var timeSince = new Date().getTime() - latestTapStamp;
  87. // double tap
  88. if(timeSince < 600 && timeSince > 0) {
  89. zoomedinTimeOut = setTimeout(() => {
  90. clearTimeout(zoomedinTimeOut);
  91. detectZoomChanged();
  92. }, doubleTapDelay);
  93. }
  94. latestTapStamp = new Date().getTime();
  95. });
  96. `;
  97. const getBaseScript = ({
  98. viewportContent,
  99. scalesPageToFit,
  100. scrollEnabledWithZoomedin,
  101. }) =>
  102. `
  103. ;
  104. if (!document.getElementById("rnahw-wrapper")) {
  105. var wrapper = document.createElement('div');
  106. wrapper.id = 'rnahw-wrapper';
  107. while (document.body.firstChild instanceof Node) {
  108. wrapper.appendChild(document.body.firstChild);
  109. }
  110. document.body.appendChild(wrapper);
  111. }
  112. ${updateSizeWithMessage('wrapper', scalesPageToFit)}
  113. window.addEventListener('load', updateSize);
  114. window.addEventListener('resize', updateSize);
  115. ${domMutationObserveScript}
  116. ${setViewportContent(viewportContent)}
  117. ${scrollEnabledWithZoomedin ? detectZoomChanged : ''}
  118. updateSize();
  119. `;
  120. const appendFilesToHead = ({files, script}) =>
  121. files.reduceRight((combinedScript, file) => {
  122. const {rel, type, href} = file;
  123. return `
  124. var link = document.createElement('link');
  125. link.rel = '${rel}';
  126. link.type = '${type}';
  127. link.href = '${href}';
  128. document.head.appendChild(link);
  129. ${combinedScript}
  130. `;
  131. }, script);
  132. const screenWidth = Dimensions.get('window').width;
  133. const bodyStyle = `
  134. body {
  135. margin: 0;
  136. padding: 0;
  137. }
  138. `;
  139. const appendStylesToHead = ({style, script}) => {
  140. const currentStyles = style ? bodyStyle + style : bodyStyle;
  141. // Escape any single quotes or newlines in the CSS with .replace()
  142. const escaped = currentStyles.replace(/\'/g, "\\'").replace(/\n/g, '\\n');
  143. return `
  144. var styleElement = document.createElement('style');
  145. styleElement.innerHTML = '${escaped}';
  146. document.head.appendChild(styleElement);
  147. ${script}
  148. `;
  149. };
  150. const getInjectedSource = ({html, script}) => `
  151. ${html}
  152. <script>
  153. // prevents code colissions with global scope
  154. (function() {
  155. ${script}
  156. })();
  157. </script>
  158. `;
  159. const getScript = ({
  160. files,
  161. customStyle,
  162. customScript,
  163. style,
  164. viewportContent,
  165. scalesPageToFit,
  166. scrollEnabledWithZoomedin,
  167. }) => {
  168. let script = getBaseScript({
  169. viewportContent,
  170. scalesPageToFit,
  171. scrollEnabledWithZoomedin,
  172. });
  173. script =
  174. files && files.length > 0 ? appendFilesToHead({files, script}) : script;
  175. script = appendStylesToHead({style: customStyle, script});
  176. customScript && (script = customScript + script);
  177. return script;
  178. };
  179. export const getWidth = (style) => {
  180. return style && style.width ? style.width : screenWidth;
  181. };
  182. export const isSizeChanged = ({
  183. height,
  184. previousHeight,
  185. width,
  186. previousWidth,
  187. }) => {
  188. if (!height || !width) {
  189. return;
  190. }
  191. return height !== previousHeight || width !== previousWidth;
  192. };
  193. export const reduceData = (props) => {
  194. const {source} = props;
  195. const script = getScript(props);
  196. const {html, baseUrl} = source;
  197. if (html) {
  198. return {
  199. currentSource: {baseUrl, html: getInjectedSource({html, script})},
  200. };
  201. } else {
  202. return {
  203. currentSource: source,
  204. script,
  205. };
  206. }
  207. };
  208. export const shouldUpdate = ({prevProps, nextProps}) => {
  209. if (!(prevProps && nextProps)) {
  210. return true;
  211. }
  212. for (const prop in nextProps) {
  213. if (nextProps[prop] !== prevProps[prop]) {
  214. if (
  215. typeof nextProps[prop] === 'object' &&
  216. typeof prevProps[prop] === 'object'
  217. ) {
  218. if (
  219. shouldUpdate({
  220. prevProps: prevProps[prop],
  221. nextProps: nextProps[prop],
  222. })
  223. ) {
  224. return true;
  225. }
  226. } else {
  227. return true;
  228. }
  229. }
  230. }
  231. return false;
  232. };