| <!DOCTYPE html> |
| <html> |
| <head> |
| <style> |
| body, html { |
| width: 100%; |
| height: 100%; |
| margin: 0; |
| } |
| |
| #iframe { |
| width: 100vw; |
| height: 100vh; |
| border: 0; |
| padding: 0; |
| overflow: scroll; |
| } |
| |
| #clip { |
| width: 50%; |
| height: 20%; |
| overflow: hidden; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="clip"> |
| <iframe id="iframe"> |
| </iframe> |
| </div> |
| <script> |
| window.loaded = false; |
| window.notifyBrowser = false; |
| const kMarginOfError = 1; |
| const tinyTimeout = 50; |
| |
| window.addEventListener("load", () => { |
| window.loaded = true; |
| if (window.notifyBrowser) |
| notifyWhenLoaded(); |
| }); |
| |
| function notifyWhenLoaded() { |
| if (loaded) |
| window.domAutomationController.send("LOADED"); |
| else |
| window.notifyBrowser = true; |
| } |
| |
| // Waits until the |visualViewport| reaches the given size (approximately). |
| // Notifies the browser when this happens. |
| function notifyVisualViewportChanged(width, height) { |
| if ((Math.abs(visualViewport.width - width) < kMarginOfError) && |
| (Math.abs(visualViewport.height - height) < kMarginOfError)) { |
| return window.domAutomationController.send("SIZED"); |
| } |
| |
| window.setTimeout(() => { |
| notifyVisualViewportChanged(width, height); |
| }, tinyTimeout); |
| } |
| |
| function isEmptyRect(rect) { |
| return (rect.width * rect.height) === 0; |
| } |
| |
| function intersect(rect1, rect2) { |
| let minX1 = rect1.x, maxX1 = minX1 + rect1.width |
| minY1 = rect1.y, maxY1 = minY1 + rect1.height, |
| minX2 = rect2.x, maxX2 = minX2 + rect2.width, |
| minY2 = rect2.y, maxY2 = minY2 + rect2.height; |
| |
| return !(isEmptyRect(rect1) || |
| isEmptyRect(rect2) || |
| (minX1 > maxX2) || |
| (minX2 > maxX1) || |
| (minY1 > maxY2) || |
| (minY2 > maxY1)); |
| } |
| |
| function visualViewportAsRect() { |
| return { |
| x: visualViewport.offsetLeft, |
| y: visualViewport.offsetTop, |
| width: Math.round(visualViewport.width), |
| height: Math.round(visualViewport.height) |
| }; |
| } |
| |
| function rectAsString(rect) { |
| return `${rect.x},${rect.y},${rect.width},${rect.height}`; |
| } |
| |
| // Waits until the given element is inside the |visualViewport|. |
| function notifyWhenVisible(el) { |
| if (intersect(el.getBoundingClientRect(), visualViewportAsRect())) { |
| window.domAutomationController.send("VISIBLE"); |
| } else { |
| window.setTimeout(() => { |
| notifyWhenVisible(el); |
| }, tinyTimeout); |
| } |
| } |
| |
| let lastLayoutViewportX; |
| let lastLayoutViewportY; |
| let lastVisualViewportX; |
| let lastVisualViewportY; |
| let lastScale; |
| function notifyWhenViewportStable(numFrames) { |
| const kUnchangedFramesConsideredStable = 10; |
| |
| if (lastLayoutViewportX !== window.scrollX || |
| lastLayoutViewportY !== window.scrollY || |
| lastVisualViewportX !== window.visualViewport.offsetLeft || |
| lastVisualViewportY !== window.visualViewport.offsetTop || |
| lastScale !== window.visualViewport.scale) { |
| lastLayoutViewportX = window.scrollX; |
| lastLayoutViewportY = window.scrollY; |
| lastVisualViewportX = window.visualViewport.offsetLeft; |
| lastVisualViewportY = window.visualViewport.offsetTop; |
| lastScale = window.visualViewport.scale; |
| numFrames = 0; |
| } |
| |
| numFrames++; |
| |
| if (numFrames == kUnchangedFramesConsideredStable) { |
| window.domAutomationController.send("VIEWPORT_STABLE"); |
| } else { |
| requestAnimationFrame(notifyWhenViewportStable.bind(null, numFrames)); |
| } |
| } |
| </script> |
| </body> |
| </html> |