Skip to content

Commit 7a158d9

Browse files
committed
feat: new DOM feature HandleHTMLDimensionsFeature
This feature will dynamically provide updates of the body size.
1 parent 873d646 commit 7a158d9

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import script from './HandleHTMLDimensionsFeature.webjs';
2+
import { FeatureBuilder } from '../FeatureBuilder';
3+
import type { RectSize } from './types';
4+
import type { FeatureConstructor } from '../Feature';
5+
import type { PropDefinition } from '../types';
6+
7+
/**
8+
* The script will check for different APIs in order to
9+
* implement the notification of HTML dimensions changes. By order of preference:
10+
* {@link https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver | ResizeObserver} (resize),
11+
* {@link https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver | MutationObserver}
12+
* (mutation) and polling.
13+
*
14+
* @public
15+
*/
16+
export type HTMLDimensionsImplementation = 'resize' | 'mutation' | 'polling';
17+
18+
/**
19+
* Options for {@link HandleHTMLDimensionsFeature}.
20+
*
21+
* @public
22+
*/
23+
export interface HandleHTMLDimensionsOptions {
24+
/**
25+
* Force a specific implementation, if the underlying API is available.
26+
*
27+
* @remarks
28+
*
29+
* This option is useful in development to force one implementation to mock older browsers.
30+
*
31+
* @defaultValue false
32+
*/
33+
forceImplementation?: HTMLDimensionsImplementation | false;
34+
35+
/**
36+
* In polling mode, at which interval should the dimensions be retrieved?
37+
*
38+
* @remarks
39+
* A value of 0 will disable polling.
40+
*
41+
* @defaultValue 200
42+
*/
43+
pollingInterval?: number;
44+
/**
45+
* The minimum difference between two updates' dimensions to trigger a change
46+
* event.
47+
*
48+
*
49+
* @defaultValue 0
50+
*/
51+
deltaMin?: number;
52+
}
53+
54+
/**
55+
* An object describing various dimensions of the HTML layout.
56+
*
57+
* @remarks
58+
* This object units are in CSS pixels. CSS pixels match device pixels when the
59+
* web page has a `<meta name="viewport" content="width=device-width" />` tag.
60+
*
61+
* @public
62+
*/
63+
export interface HTMLDimensions {
64+
/**
65+
* The layout viewport size, e.g. the size of the WebView in device pixels.
66+
*/
67+
layoutViewport: RectSize;
68+
69+
/**
70+
* The content size, e.g. the size of the body element in CSS pixels.
71+
*/
72+
content: RectSize;
73+
74+
/**
75+
* Which implementation has been used to generate this event?
76+
* See {@link HTMLDimensionsImplementation}.
77+
*/
78+
implementation: HTMLDimensionsImplementation;
79+
}
80+
81+
const defaultOptions: HandleHTMLDimensionsOptions = {
82+
deltaMin: 0,
83+
forceImplementation: false,
84+
pollingInterval: 200
85+
};
86+
87+
/**
88+
* This feature enables receiving various dimensions relative to the layout. The events
89+
* will only be fired when a change is observed to either the layout or the content size.
90+
*
91+
* @remarks
92+
* If you need to guarantee that 1 CSS pixel = 1 Device pixel, you should use this
93+
* feature with a meta tag viewport setting width to device width.
94+
* {@link ForceResponsiveViewportFeature}
95+
*
96+
* @public
97+
*/
98+
export const HandleHTMLDimensionsFeature: FeatureConstructor<
99+
HandleHTMLDimensionsOptions,
100+
[
101+
PropDefinition<{
102+
onDOMHTMLDimensions?: (d: HTMLDimensions) => void;
103+
}>
104+
]
105+
> = new FeatureBuilder({
106+
script,
107+
defaultOptions,
108+
className: 'HandleHTMLDimensionsFeature',
109+
featureIdentifier: 'org.formidable-webview/webshell.handle-html-dimensions'
110+
})
111+
.withEventHandlerProp<HTMLDimensions, 'onDOMHTMLDimensions'>(
112+
'onDOMHTMLDimensions'
113+
)
114+
.build();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
function HandleHTMLDimensionsFeature(context) {
2+
var postMessage = context.postMessage;
3+
var options = context.options || {};
4+
var forceImplementation = options.forceImplementation || false;
5+
var pollingInterval =
6+
typeof options.pollingInterval === 'number' ? options.pollingInterval : 200;
7+
var deltaMin = options.deltaMin || 0;
8+
var oldDimensions = {
9+
layoutViewport: { width: 0, height: 0 },
10+
content: { width: 0, height: 0 }
11+
};
12+
var MutationObserver =
13+
window['MutationObserver'] || window['WebKitMutationObserver'];
14+
function extractNumericValue(pixelString) {
15+
return pixelString ? parseFloat(pixelString.match(/[\d.]+/)) : 0;
16+
}
17+
function dimensionsAreEqual(d1, d2) {
18+
return (
19+
Math.abs(d1.layoutViewport.width - d2.layoutViewport.width) < deltaMin &&
20+
Math.abs(d1.layoutViewport.height - d2.layoutViewport.height) <
21+
deltaMin &&
22+
Math.abs(d1.content.width - d2.content.width) < deltaMin &&
23+
Math.abs(d1.content.height - d2.content.height) < deltaMin &&
24+
d1.implementation === d2.implementation
25+
);
26+
}
27+
function createPostDimensions(implementation) {
28+
return context.makeCallbackSafe(function postSize() {
29+
var bodyStyle = getComputedStyle(document.body);
30+
var bodySize = document.body.getBoundingClientRect();
31+
var marginRight = extractNumericValue(bodyStyle.marginRight);
32+
var marginBottom = extractNumericValue(bodyStyle.marginBottom);
33+
var layoutViewport = {
34+
width: document.documentElement.clientWidth,
35+
height: document.documentElement.clientHeight
36+
};
37+
var dimensions = {
38+
implementation: implementation,
39+
layoutViewport: layoutViewport,
40+
content: {
41+
width: bodySize.right + marginRight,
42+
height: bodySize.bottom + marginBottom
43+
}
44+
};
45+
if (!dimensionsAreEqual(oldDimensions, dimensions)) {
46+
postMessage(dimensions);
47+
oldDimensions = dimensions;
48+
}
49+
});
50+
}
51+
if (
52+
window.ResizeObserver &&
53+
(forceImplementation === false || forceImplementation === 'resize')
54+
) {
55+
// resize mode
56+
var resizePostDimensions = createPostDimensions('resize');
57+
var resizeObserver = new window.ResizeObserver(resizePostDimensions);
58+
resizeObserver.observe(document.body);
59+
resizePostDimensions();
60+
} else if (
61+
MutationObserver &&
62+
(forceImplementation === false || forceImplementation === 'mutation')
63+
) {
64+
// mutation mode
65+
var observerPostDimensions = createPostDimensions('mutation');
66+
window.addEventListener('resize', observerPostDimensions);
67+
var observer = new MutationObserver(observerPostDimensions);
68+
observer.observe(document, {
69+
subtree: true,
70+
attributes: true
71+
});
72+
observerPostDimensions();
73+
} else {
74+
// polling mode
75+
var pollingPostDimensions = createPostDimensions('polling');
76+
window.addEventListener('resize', pollingPostDimensions);
77+
setInterval(pollingPostDimensions, pollingInterval);
78+
pollingPostDimensions();
79+
context.warn(
80+
"This browser doesn't support either MutationObserver or ResizeObserver." +
81+
'The dimensions will still be read every' +
82+
pollingInterval +
83+
'ms and committed when a change is observed.'
84+
);
85+
}
86+
}

0 commit comments

Comments
 (0)