├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── example
├── .gitignore
├── App.js
├── image.png
├── package.json
└── yarn.lock
├── expo-browser-polyfill-1.0.1.tgz
├── package.json
├── src
├── DOM
│ ├── Document.js
│ ├── Element.js
│ ├── HTMLCanvasElement.js
│ ├── HTMLImageElement.js
│ ├── HTMLVideoElement.js
│ └── Node.js
├── console
│ ├── count.js
│ ├── index.js
│ └── time.js
├── index.js
├── module.js
├── module.native.js
├── performance.js
├── process.js
├── resize.js
└── window.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["babel-preset-expo"],
3 | "env": {
4 | "development": {
5 | "plugins": ["transform-react-jsx-source"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /example
3 | /node_modules
4 | test
5 |
6 | .babelrc
7 | npm-debug.log
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-present, 650 Industries, Inc. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @expo/browser-polyfill
2 |
3 | Browser polyfill for React Native
4 |
5 | ### Installation
6 |
7 | ```bash
8 | yarn add @expo/browser-polyfill
9 | ```
10 |
11 | ### Usage
12 |
13 | Import the library into your JavaScript file:
14 |
15 | ```js
16 | import '@expo/browser-polyfill';
17 | ```
18 |
19 | ## Implements
20 |
21 | ## DOM
22 |
23 | DOM is provided with very low support, these are used for libs like pixi.js that validate type.
24 |
25 | ```js
26 | class Node
27 | class Element
28 | class Document
29 | class HTMLImageElement
30 | class Image
31 | class ImageBitmap
32 | class HTMLVideoElement
33 | class Video
34 | class HTMLCanvasElement
35 | class Canvas
36 | ```
37 |
38 | ### Image, HTMLImageElement, ImageBitmap
39 |
40 | Image has support for loading callbacks, however the loaded uri must be passed to the src already.
41 |
42 | ```js
43 | const image = new Image();
44 | image.src = '';
45 | image.onload = () => {
46 | const { src, width, height } = image;
47 | };
48 | image.addEventListener('loading', () => {});
49 | image.addEventListener('error', () => {});
50 | ```
51 |
52 | ### Document
53 |
54 | ```js
55 | const element = document.createElement('div');
56 | const fakeContext = element.getContext('');
57 | ```
58 |
59 | ### Element
60 |
61 | #### All sizes return the window size:
62 |
63 | ```js
64 | element.clientWidth;
65 | element.clientHeight;
66 | element.innerWidth;
67 | element.innerHeight;
68 | element.offsetWidth;
69 | element.offsetHeight;
70 | ```
71 |
72 | #### Empty attributes that prevent libraries from crashing
73 |
74 | ```js
75 | element.tagName;
76 | element.addEventListener;
77 | element.removeEventListener;
78 | element.setAttributeNS;
79 | element.createElementNS;
80 | ```
81 |
82 | ### Node
83 |
84 | ```js
85 | node.ownerDocument;
86 | node.className;
87 | node.appendChild;
88 | node.insertBefore;
89 | node.removeChild;
90 | node.setAttributeNS;
91 | node.getBoundingClientRect;
92 | ```
93 |
94 | # External Libraries
95 |
96 | Some external node.js polyfills are added as well.
97 |
98 | ## [text-encoding](https://github.com/inexorabletash/text-encoding)
99 |
100 | ```
101 | global.TextEncoder
102 | global.TextDecoder
103 | ```
104 |
105 | ## [xmldom-qsa](https://github.com/zeligzhou/xmldom-qsa)
106 |
107 | ```
108 | window.DOMParser
109 | ```
110 |
111 | ## [react-native-console-time-polyfill](https://github.com/MaxGraey/react-native-console-time-polyfill)
112 |
113 | ```
114 | console.time(label);
115 | console.timeEnd(label);
116 | console.count(label);
117 | ```
118 |
119 | # Debug flags
120 |
121 | For debugging base64 image transformations toggle:
122 |
123 | ```js
124 | global.__debug_browser_polyfill_image = true;
125 | ```
126 |
127 | By default `global.__debug_browser_polyfill_image` is false.
128 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 |
--------------------------------------------------------------------------------
/example/App.js:
--------------------------------------------------------------------------------
1 | import "@expo/browser-polyfill";
2 |
3 | import { Asset } from "expo-asset";
4 | import Constants from "expo-constants";
5 | import * as FileSystem from "expo-file-system";
6 | import { GLView } from "expo-gl";
7 | import React from "react";
8 | import { View, Text } from "react-native";
9 |
10 | /*
11 | Issue: https://github.com/expo/browser-polyfill/issues/2
12 | Firebase doesn't work with this lib.
13 | Adding it here to test.
14 | */
15 | import { initializeApp } from "firebase/app";
16 | import { onAuthStateChanged, getAuth, signInAnonymously } from "firebase/auth";
17 | import * as database from "firebase/database";
18 |
19 | function setupFirebase() {
20 | var config = {
21 | apiKey: "AIzaSyAfgPq82VdNqEZ8hqnOvYdD7kSPiFK9W1k",
22 | authDomain: "keira-knightley-51df6.firebaseapp.com",
23 | databaseURL: "https://keira-knightley-51df6.firebaseio.com",
24 | projectId: "keira-knightley-51df6",
25 | storageBucket: "keira-knightley-51df6.appspot.com",
26 | messagingSenderId: "628588079444",
27 | };
28 | initializeApp(config);
29 | // app.firestore().settings({ timestampsInSnapshots: true });
30 |
31 | const _onAuthStateChanged = async (user) => {
32 | // app.database().forceDisallow();
33 | // console.log(firebase.database());
34 | // return;
35 | if (!user) {
36 | try {
37 | await signInAnonymously(getAuth());
38 | } catch ({ message }) {
39 | alert(message);
40 | }
41 | } else {
42 | console.log("signed in");
43 | const testRef = database.ref(database.getDatabase(), "/test_data");
44 | try {
45 | const snapshot = await database.get(testRef);
46 | const key = snapshot.key;
47 | const value = snapshot.val();
48 | console.log("Firebase Database: ", { key, value });
49 | } catch ({ message }) {
50 | console.error("Error: Firebase Database: ", message);
51 | }
52 | }
53 | };
54 |
55 | const auth = getAuth();
56 |
57 | onAuthStateChanged(auth, _onAuthStateChanged);
58 | }
59 |
60 | const tests = {
61 | timer: () => {
62 | const tag = "test-timer";
63 | console.time(tag);
64 | console.timeEnd(tag);
65 | console.count(tag);
66 | },
67 | userAgent: () => {
68 | console.log("userAgent: ", global.userAgent);
69 | },
70 | process: () => {
71 | console.log("process: ", global.process);
72 | },
73 | navigator: () => {
74 | console.log("navigator: ", Object.keys(global.navigator));
75 | },
76 | performance: () => {
77 | console.log("performance: ", Object.keys(global.performance));
78 | },
79 | window: () => {
80 | console.log("location: ", Object.keys(window.location));
81 | },
82 | base64: async () => {
83 | const asset = Asset.fromModule(require("./image.png"));
84 | await asset.downloadAsync();
85 | console.log("B64: ASSET:", asset.localUri);
86 | const data = (await FileSystem.readAsStringAsync(asset.localUri, {
87 | encoding: FileSystem.EncodingType.Base64,
88 | })).trim();
89 |
90 | global.__debug_browser_polyfill_image = true;
91 | const pngPrefix = "data:image/png;base64,";
92 | // console.log('B64: DATA: ', pngPrefix + data);
93 | const image = new global.HTMLImageElement();
94 | image.addEventListener("loading", () => {
95 | console.log("B64: Loading Image");
96 | });
97 | image.addEventListener("error", () => {
98 | console.log("B64: Error Loading Image");
99 | });
100 | image.onload = () => {
101 | const { src, width, height } = image;
102 | console.log("B64: Loaded Image", { src, width, height });
103 | };
104 | image.src = pngPrefix + data;
105 | },
106 | correctElementsCreated: () => {
107 | const {
108 | HTMLImageElement,
109 | ImageBitmap,
110 | HTMLVideoElement,
111 | HTMLCanvasElement,
112 | } = global;
113 | const types = [
114 | { type: "img", inst: HTMLImageElement },
115 | { type: "video", inst: HTMLVideoElement },
116 | { type: "canvas", inst: HTMLCanvasElement },
117 | { type: "img", inst: ImageBitmap },
118 | ];
119 | types.forEach((item) => {
120 | const element = document.createElement(item.type);
121 | console.log(
122 | "type",
123 | item.type,
124 | element instanceof item.inst,
125 | element instanceof Number
126 | );
127 | });
128 | },
129 | elements: () => {
130 | const {
131 | HTMLImageElement,
132 | Image,
133 | ImageBitmap,
134 | HTMLVideoElement,
135 | Video,
136 | HTMLCanvasElement,
137 | Canvas,
138 | } = global;
139 | const elements = {
140 | HTMLImageElement,
141 | Image,
142 | ImageBitmap,
143 | HTMLVideoElement,
144 | Video,
145 | HTMLCanvasElement,
146 | Canvas,
147 | };
148 | console.log(
149 | "Elements: ",
150 | Object.keys(elements).map((key) => ({ [key]: !!elements[key] }))
151 | );
152 | },
153 | loadImage: () => {
154 | const image = new global.HTMLImageElement();
155 | image.addEventListener("loading", () => {
156 | console.log("Loading Image");
157 | });
158 | image.addEventListener("error", () => {
159 | console.log("Error Loading Image");
160 | });
161 | image.onload = () => {
162 | const { src, width, height } = image;
163 | console.log("Loaded Image", { src, width, height });
164 | };
165 | image.src = "https://avatars0.githubusercontent.com/u/9664363?s=40&v=4";
166 | },
167 | textDecoder: () => {
168 | console.log("TextDecoder: ", !!global.TextDecoder);
169 | const utfLabel = "utf-8";
170 | const options = { fatal: true };
171 | const decoder = new TextDecoder(utfLabel, options);
172 |
173 | let data = "{}";
174 | let buffer = "";
175 | buffer += decoder.decode(data, { stream: true });
176 | console.log("TextDecoder buffer", buffer, " from: ", Object.keys(data));
177 | },
178 | context: () => {
179 | const canvas = document.createElement("canvas");
180 | const context = canvas.getContext("2d");
181 | const webgl = canvas.getContext("webgl");
182 | console.log(context, webgl);
183 | },
184 | domParser: () => {
185 | console.log("window.DOMParser: ", !!window.DOMParser);
186 | const parser = new window.DOMParser();
187 |
188 | const html = `
189 |
190 |
191 |
192 |
193 | some text content
194 |
195 |
is my dad.
196 |
197 |
198 |
199 |
200 | `;
201 | const dom = parser.parseFromString(html);
202 | const container = dom.getElementById("container-id");
203 | Object.keys(container).forEach((key) => {
204 | const obj = container[key];
205 | console.log(`DOMParser: container.${key}: ${obj}`);
206 | });
207 | },
208 | };
209 |
210 | const testGL = !Constants.isDevice;
211 |
212 | function useWindowDeviceSize() {
213 | const [size, setSize] = React.useState(null);
214 | React.useEffect(() => {
215 | const onLayout = () => {
216 | setSize({
217 | width: window.innerWidth,
218 | height: window.innerHeight,
219 | scale: window.devicePixelRatio,
220 | });
221 | console.log("Update Layout:", {
222 | width: window.innerWidth,
223 | height: window.innerHeight,
224 | scale: window.devicePixelRatio,
225 | });
226 | };
227 |
228 | window.addEventListener("resize", onLayout);
229 |
230 | return () => {
231 | window.removeEventListener("resize", onLayout);
232 | };
233 | }, []);
234 | return size;
235 | }
236 |
237 | export default function App() {
238 | const size = useWindowDeviceSize();
239 |
240 | React.useEffect(() => {
241 | if (!testGL) {
242 | runTests();
243 | }
244 | setupFirebase();
245 | }, []);
246 |
247 | const runTests = () => {
248 | Object.keys(tests).forEach((key) => {
249 | try {
250 | console.log("Run Test: ", key);
251 | tests[key]();
252 | } catch (error) {
253 | console.error(`Error running ${key} test: `, error);
254 | }
255 | });
256 | };
257 |
258 | return (
259 |
260 | Check your console...
261 | {testGL && (
262 | {
264 | global.__context = context;
265 | runTests();
266 | }}
267 | />
268 | )}
269 |
270 | );
271 | }
272 |
--------------------------------------------------------------------------------
/example/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/expo/browser-polyfill/36d63330cfe04a8502e7608fc65a986cec1c26aa/example/image.png
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@expo/browser-polyfill": "../expo-browser-polyfill-1.0.1.tgz",
4 | "expo": "^44.0.4",
5 | "expo-2d-context": "^0.0.3",
6 | "expo-file-system": "^13.2.0",
7 | "expo-gl": "^11.1.1",
8 | "firebase": "^9.6.2",
9 | "react": "17.0.1",
10 | "react-native": "0.64.3",
11 | "text-encoding": "^0.7.0",
12 | "xmldom-qsa": "^1.0.3"
13 | },
14 | "devDependencies": {
15 | "@babel/core": "^7.12.9"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/expo-browser-polyfill-1.0.1.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/expo/browser-polyfill/36d63330cfe04a8502e7608fc65a986cec1c26aa/expo-browser-polyfill-1.0.1.tgz
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@expo/browser-polyfill",
3 | "version": "1.0.1",
4 | "sideEffects": false,
5 | "description": "Browser polyfill for making React Native compatible with web libs like pixi.js, three.js, phaser.js",
6 | "homepage": "https://github.com/expo/browser-polyfill#readme",
7 | "bugs": {
8 | "url": "https://github.com/expo/browser-polyfill/issues"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/expo/browser-polyfill.git"
13 | },
14 | "keywords": [
15 | "expo",
16 | "browser",
17 | "polyfill",
18 | "react-native",
19 | "react",
20 | "web",
21 | "dom",
22 | "document",
23 | "shim"
24 | ],
25 | "private": false,
26 | "author": {
27 | "email": "bacon@expo.io",
28 | "name": "Evan Bacon"
29 | },
30 | "license": "MIT",
31 | "files": [
32 | "src"
33 | ],
34 | "pre-push": [
35 | "lint"
36 | ],
37 | "directories": {
38 | "example": "examples",
39 | "lib": "src"
40 | },
41 | "readmeFilename": "README.md",
42 | "main": "src/index",
43 | "scripts": {
44 | "lint:example": "eslint example/",
45 | "lint": "eslint src/",
46 | "lint:fix": "eslint src/ --fix",
47 | "sync-example": "rsync -rv src example/node_modules/@expo/browser-polyfill && rsync -rv package.json example/node_modules/@expo/browser-polyfill"
48 | },
49 | "peerDependencies": {
50 | "expo-file-system": "^13.2.0",
51 | "react": "^17.0.1",
52 | "react-native": "^0.64.3"
53 | },
54 | "devDependencies": {
55 | "babel-preset-expo": "^5.1.1",
56 | "eslint": "^5.16.0",
57 | "eslint-config-universe": "^1.0.7",
58 | "eslint-plugin-jest": "^22.4.1",
59 | "husky": "^1.3.1",
60 | "jest": "^24.7.1",
61 | "jest-expo": "^32.0.0",
62 | "prettier": "^1.17.0"
63 | },
64 | "dependencies": {
65 | "expo-2d-context": "^0.0.3",
66 | "fbemitter": "^2.1.1",
67 | "text-encoding": "^0.7.0",
68 | "uuid": "^8.3.2",
69 | "xmldom-qsa": "^1.0.3"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/DOM/Document.js:
--------------------------------------------------------------------------------
1 | import Element from './Element';
2 | import HTMLVideoElement from './HTMLVideoElement';
3 | import HTMLImageElement from './HTMLImageElement';
4 | import HTMLCanvasElement from './HTMLCanvasElement';
5 |
6 | class Document extends Element {
7 | constructor() {
8 | super('#document');
9 | this.body = new Element('BODY');
10 | this.documentElement = new Element('HTML');
11 | this.readyState = 'complete';
12 | }
13 |
14 | createElement(tagName) {
15 | switch ((tagName || '').toLowerCase()) {
16 | case 'video':
17 | return new HTMLVideoElement(tagName);
18 | case 'img':
19 | return new HTMLImageElement(tagName);
20 | case 'canvas':
21 | return new HTMLCanvasElement(tagName);
22 | case 'iframe':
23 | // Return nothing to keep firebase working.
24 | return null;
25 | default:
26 | return new Element(tagName);
27 | }
28 | }
29 |
30 | createElementNS(tagName) {
31 | const element = this.createElement(tagName);
32 | element.toDataURL = () => ({});
33 | return element;
34 | }
35 |
36 | getElementById(id) {
37 | return new Element('div');
38 | }
39 | }
40 |
41 | export default Document;
42 |
--------------------------------------------------------------------------------
/src/DOM/Element.js:
--------------------------------------------------------------------------------
1 | import Node from './Node';
2 | import CanvasRenderingContext2D from 'expo-2d-context';
3 |
4 | class Element extends Node {
5 | constructor(tagName) {
6 | return super(tagName.toUpperCase());
7 |
8 | this.doc = {
9 | body: {
10 | innerHTML: '',
11 | },
12 | };
13 | }
14 |
15 | get tagName() {
16 | return this.nodeName;
17 | }
18 |
19 | setAttributeNS() {}
20 |
21 | get clientWidth() {
22 | return this.innerWidth;
23 | }
24 | get clientHeight() {
25 | return this.innerHeight;
26 | }
27 |
28 | get offsetWidth() {
29 | return this.innerWidth;
30 | }
31 | get offsetHeight() {
32 | return this.innerHeight;
33 | }
34 |
35 | get innerWidth() {
36 | return window.innerWidth;
37 | }
38 | get innerHeight() {
39 | return window.innerHeight;
40 | }
41 |
42 | getContext(contextType, contextOptions, context) {
43 | const possibleContext = context || global.__context;
44 | if (contextType != '2d' && possibleContext) {
45 | return possibleContext;
46 | }
47 | if (contextType === '2d' && possibleContext) {
48 | return new CanvasRenderingContext2D(possibleContext);
49 | }
50 |
51 | return {
52 | fillText: (text, x, y, maxWidth) => ({}),
53 | measureText: text => ({
54 | width: (text || '').split('').length * 6,
55 | height: 24,
56 | }),
57 | fillRect: () => ({}),
58 | drawImage: () => ({}),
59 | getImageData: () => ({ data: new Uint8ClampedArray([255, 0, 0, 0]) }),
60 | getContextAttributes: () => ({
61 | stencil: true,
62 | }),
63 | getExtension: () => ({
64 | loseContext: () => {},
65 | }),
66 | putImageData: () => ({}),
67 | createImageData: () => ({}),
68 | };
69 | }
70 |
71 | get ontouchstart() {
72 | return {};
73 | }
74 | }
75 |
76 | export default Element;
77 |
--------------------------------------------------------------------------------
/src/DOM/HTMLCanvasElement.js:
--------------------------------------------------------------------------------
1 | import Element from './Element';
2 | class HTMLCanvasElement extends Element {}
3 | export default HTMLCanvasElement;
4 |
--------------------------------------------------------------------------------
/src/DOM/HTMLImageElement.js:
--------------------------------------------------------------------------------
1 | import { Image } from 'react-native';
2 | import * as FileSystem from 'expo-file-system';
3 | const { writeAsStringAsync, documentDirectory } = FileSystem;
4 | const EncodingType = FileSystem.EncodingType || FileSystem.EncodingTypes;
5 |
6 | import { v1 as uuidv1 } from 'uuid';
7 |
8 | import Element from './Element';
9 |
10 | const b64Extensions = {
11 | '/': 'jpg',
12 | i: 'png',
13 | R: 'gif',
14 | U: 'webp',
15 | };
16 |
17 | function b64WithoutPrefix(b64) {
18 | return b64.split(',')[1];
19 | }
20 |
21 | function getMIMEforBase64String(b64) {
22 | let input = b64;
23 | if (b64.includes(',')) {
24 | input = b64WithoutPrefix(b64);
25 | }
26 | const first = input.charAt(0);
27 | const mime = b64Extensions[first];
28 | if (!mime) {
29 | throw new Error('Unknown Base64 MIME type: ', b64);
30 | }
31 | return mime;
32 | }
33 |
34 | class HTMLImageElement extends Element {
35 | get src() {
36 | return this.localUri;
37 | }
38 |
39 | set src(value) {
40 | this.localUri = value;
41 | this._load();
42 | }
43 |
44 | get onload() {
45 | return this._onload;
46 | }
47 | set onload(value) {
48 | this._onload = value;
49 | }
50 |
51 | get complete() {
52 | return this._complete;
53 | }
54 | set complete(value) {
55 | this._complete = value;
56 | if (value) {
57 | this.emitter.emit('load', this);
58 | this.onload();
59 | }
60 | }
61 |
62 | constructor(props) {
63 | super('img');
64 | this._load = this._load.bind(this);
65 | this._onload = () => {};
66 | if (props !== null && typeof props === 'object') {
67 | const { localUri, width, height } = props || {};
68 | this.src = localUri;
69 | this.width = width;
70 | this.height = height;
71 | this._load();
72 | }
73 | }
74 |
75 | _load() {
76 | if (this.src) {
77 | if (
78 | typeof this.src === 'string' &&
79 | this.src.startsWith &&
80 | this.src.startsWith('data:')
81 | ) {
82 | // is base64 - convert and try again;
83 | this._base64 = this.src;
84 | const base64result = this.src.split(',')[1];
85 | (async () => {
86 | try {
87 | const MIME = getMIMEforBase64String(base64result);
88 | this.localUri = `${documentDirectory}${uuidv1()}-b64image.${MIME}`;
89 | await writeAsStringAsync(this.localUri, base64result, {
90 | encoding: EncodingType.Base64,
91 | });
92 | this._load();
93 | } catch (error) {
94 | if (global.__debug_browser_polyfill_image) {
95 | console.log(`@expo/browser-polyfill: Error:`, error.message);
96 | }
97 | this.emitter.emit('error', { target: this, error });
98 | }
99 | })();
100 | return;
101 | }
102 | if (!this.width || !this.height) {
103 | this.complete = false;
104 | this.emitter.emit('loading', this);
105 | Image.getSize(
106 | this.src,
107 | (width, height) => {
108 | this.width = width;
109 | this.height = height;
110 | this.complete = true;
111 | },
112 | error => {
113 | this.emitter.emit('error', { target: this });
114 | },
115 | );
116 | } else {
117 | this.complete = true;
118 | }
119 | }
120 | }
121 | }
122 | export default HTMLImageElement;
123 |
--------------------------------------------------------------------------------
/src/DOM/HTMLVideoElement.js:
--------------------------------------------------------------------------------
1 | import Element from './Element';
2 | class HTMLVideoElement extends Element {}
3 | export default HTMLVideoElement;
4 |
--------------------------------------------------------------------------------
/src/DOM/Node.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { EventEmitter } from 'fbemitter';
3 |
4 | class Node {
5 | constructor(nodeName) {
6 | this.emitter = new EventEmitter();
7 | this.addEventListener = this.addEventListener.bind(this);
8 | this.removeEventListener = this.removeEventListener.bind(this);
9 | this._checkEmitter = this._checkEmitter.bind(this);
10 |
11 | this.style = {};
12 | this.className = {
13 | baseVal: '',
14 | };
15 | this.nodeName = nodeName;
16 | }
17 |
18 | get ownerDocument() {
19 | return window.document;
20 | }
21 |
22 | _checkEmitter() {
23 | if (
24 | !this.emitter ||
25 | !(this.emitter.on || this.emitter.addEventListener || this.emitter.addListener)
26 | ) {
27 | this.emitter = new EventEmitter();
28 | }
29 | }
30 |
31 | addEventListener(eventName, listener) {
32 | this._checkEmitter();
33 | if (this.emitter.on) {
34 | this.emitter.on(eventName, listener);
35 | } else if (this.emitter.addEventListener) {
36 | this.emitter.addEventListener(eventName, listener);
37 | } else if (this.emitter.addListener) {
38 | this.emitter.addListener(eventName, listener);
39 | }
40 | }
41 |
42 | removeEventListener(eventName, listener) {
43 | this._checkEmitter();
44 | if (this.emitter.off) {
45 | this.emitter.off(eventName, listener);
46 | } else if (this.emitter.removeEventListener) {
47 | this.emitter.removeEventListener(eventName, listener);
48 | } else if (this.emitter.removeListener) {
49 | this.emitter.removeListener(eventName, listener);
50 | }
51 | }
52 |
53 | appendChild() {}
54 | insertBefore() {}
55 | removeChild() {}
56 | setAttributeNS() {}
57 |
58 | getBoundingClientRect() {
59 | return {
60 | left: 0,
61 | top: 0,
62 | right: window.innerWidth,
63 | bottom: window.innerHeight,
64 | x: 0,
65 | y: 0,
66 | width: window.innerWidth,
67 | height: window.innerHeight,
68 | };
69 | }
70 | }
71 |
72 | export default Node;
73 |
--------------------------------------------------------------------------------
/src/console/count.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | if (!console.count) {
4 | const counts = {};
5 |
6 | console.count = ((label = '') => {
7 | if (!counts[label])
8 | counts[label] = 0;
9 | counts[label]++;
10 | console.log(`${label}: ${counts[label]}`);
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/src/console/index.js:
--------------------------------------------------------------------------------
1 | import './count';
2 | import './time';
--------------------------------------------------------------------------------
/src/console/time.js:
--------------------------------------------------------------------------------
1 |
2 | const startTimes = {};
3 |
4 | if (!console.time) {
5 | console.time = (label => {
6 | startTimes[label] = window.performance.now();
7 | });
8 | }
9 |
10 | if (!console.timeEnd) {
11 | console.timeEnd = (label => {
12 | const endTime = window.performance.now();
13 | if (startTimes[label]) {
14 | const delta = endTime - startTimes[label];
15 | console.log(`${label}: ${delta.toFixed(3)}ms`);
16 | delete startTimes[label];
17 | } else {
18 | console.warn(`Warning: No such label '${label}' for console.timeEnd()`);
19 | }
20 | });
21 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import "./module";
2 |
--------------------------------------------------------------------------------
/src/module.js:
--------------------------------------------------------------------------------
1 | // do nothing :)
2 |
--------------------------------------------------------------------------------
/src/module.native.js:
--------------------------------------------------------------------------------
1 | import Document from './DOM/Document';
2 |
3 | import './window';
4 | import './resize';
5 | import './process';
6 | import './console';
7 | window.document = window.document || new Document();
8 |
--------------------------------------------------------------------------------
/src/performance.js:
--------------------------------------------------------------------------------
1 | if (!global.nativePerformanceNow) {
2 | global.nativePerformanceNow = global.nativePerformanceNow || global.performanceNow || require('fbjs/lib/performanceNow');
3 | global.performanceNow = global.performanceNow || global.nativePerformanceNow;
4 | }
5 |
6 | if (!window.performance || !window.performance.now) {
7 | window.performance = {
8 | timeOrigin: -1,
9 | timing: {
10 | connectEnd: -1,
11 | connectStart: -1,
12 | domComplete: -1,
13 | domContentLoadedEventEnd: -1,
14 | domContentLoadedEventStart: -1,
15 | domInteractive: -1,
16 | domLoading: -1,
17 | domainLookupEnd: -1,
18 | domainLookupStart: -1,
19 | fetchStart: -1,
20 | loadEventEnd: -1,
21 | loadEventStart: -1,
22 | navigationStart: -1,
23 | redirectEnd: -1,
24 | redirectStart: -1,
25 | requestStart: -1,
26 | responseEnd: -1,
27 | responseStart: -1,
28 | secureConnectionStart: -1,
29 | unloadEventEnd: -1,
30 | unloadEventStart: -1,
31 | },
32 | now() {
33 | // TODO: Bacon: Return DOMHighResTimeStamp
34 | return global.nativePerformanceNow();
35 | },
36 | toJSON() {
37 | return {
38 | timing: this.timing,
39 | navigation: this.navigation,
40 | timeOrigin: this.timeOrigin
41 | }
42 | },
43 | navigation: {
44 | redirectCount: -1,
45 | type: -1
46 | },
47 | memory: {
48 | jsHeapSizeLimit: -1,
49 | totalJSHeapSize: -1,
50 | usedJSHeapSize: -1
51 | },
52 | measure() {
53 | console.warn('window.performance.measure is not implemented')
54 | },
55 | mark() {
56 | console.warn('window.performance.mark is not implemented')
57 | },
58 | clearMeasures() {
59 | console.warn('window.performance.clearMeasures is not implemented')
60 | },
61 | clearMarks() {
62 | console.warn('window.performance.clearMarks is not implemented')
63 | },
64 | clearResourceTimings() {
65 | console.warn('window.performance.clearResourceTimings is not implemented')
66 | },
67 | setResourceTimingBufferSize() {
68 | console.warn('window.performance.setResourceTimingBufferSize is not implemented')
69 | },
70 | getEntriesByType() {
71 | console.warn('window.performance.getEntriesByType is not implemented')
72 | return [];
73 | },
74 | getEntriesByName() {
75 | console.warn('window.performance.getEntriesByName is not implemented')
76 | return [];
77 | },
78 | getEntries() {
79 | console.warn('window.performance.getEntries is not implemented')
80 | }
81 | }
82 | }
83 |
84 | export default window.performance;
--------------------------------------------------------------------------------
/src/process.js:
--------------------------------------------------------------------------------
1 | process.browser = true;
2 |
--------------------------------------------------------------------------------
/src/resize.js:
--------------------------------------------------------------------------------
1 | import { Dimensions } from 'react-native';
2 | const { width, height, scale } = Dimensions.get('window');
3 | /*
4 | Window Resize Stub
5 | */
6 | window.devicePixelRatio = scale;
7 | window.innerWidth = width;
8 | window.clientWidth = width;
9 | window.innerHeight = height;
10 | window.clientHeight = height;
11 | window.screen = window.screen || {};
12 | window.screen.orientation =
13 | window.screen.orientation || window.clientWidth < window.clientHeight ? 0 : 90;
14 |
15 | if (!global.__EXPO_BROWSER_POLYFILL_RESIZE) {
16 | global.__EXPO_BROWSER_POLYFILL_RESIZE = true;
17 | Dimensions.addEventListener('change', ({ screen: { width, height, scale } }) => {
18 | window.devicePixelRatio = scale;
19 | window.innerWidth = width;
20 | window.clientWidth = width;
21 | window.innerHeight = height;
22 | window.clientHeight = height;
23 |
24 | window.screen.orientation = width < height ? 0 : 90;
25 | if (window.emitter && window.emitter.emit) {
26 | window.emitter.emit('resize');
27 | }
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/src/window.js:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'fbemitter';
2 | import { TextDecoder, TextEncoder } from 'text-encoding';
3 | import Document from './DOM/Document';
4 |
5 | import './performance';
6 |
7 | import HTMLImageElement from './DOM/HTMLImageElement';
8 | import HTMLCanvasElement from './DOM/HTMLCanvasElement';
9 | import HTMLVideoElement from './DOM/HTMLVideoElement';
10 | import CanvasRenderingContext2D from 'expo-2d-context';
11 |
12 | global.HTMLImageElement = global.HTMLImageElement || HTMLImageElement;
13 | global.Image = global.Image || HTMLImageElement;
14 | global.ImageBitmap = global.ImageBitmap || HTMLImageElement;
15 | global.HTMLVideoElement = global.HTMLVideoElement || HTMLVideoElement;
16 | global.Video = global.Video || HTMLVideoElement;
17 | global.HTMLCanvasElement = global.HTMLCanvasElement || HTMLCanvasElement;
18 | global.Canvas = global.Canvas || HTMLCanvasElement;
19 | global.CanvasRenderingContext2D =
20 | global.CanvasRenderingContext2D || CanvasRenderingContext2D;
21 | // This causes the cryptic error:
22 | // `Value is undefined, expected an Object`
23 | // global.WebGLRenderingContext = global.WebGLRenderingContext || function() {};
24 |
25 | function checkEmitter() {
26 | if (
27 | !window.emitter ||
28 | !(
29 | window.emitter.on ||
30 | window.emitter.addEventListener ||
31 | window.emitter.addListener
32 | )
33 | ) {
34 | window.emitter = new EventEmitter();
35 | }
36 | }
37 |
38 | window.scrollTo = window.scrollTo || (() => ({}));
39 |
40 | window.addEventListener = (eventName, listener) => {
41 | checkEmitter();
42 | const addListener = () => {
43 | if (window.emitter.on) {
44 | window.emitter.on(eventName, listener);
45 | } else if (window.emitter.addEventListener) {
46 | window.emitter.addEventListener(eventName, listener);
47 | } else if (window.emitter.addListener) {
48 | window.emitter.addListener(eventName, listener);
49 | }
50 | };
51 |
52 | addListener();
53 |
54 | if (eventName.toLowerCase() === 'load') {
55 | if (window.emitter && window.emitter.emit) {
56 | setTimeout(() => {
57 | window.emitter.emit('load');
58 | }, 1);
59 | }
60 | }
61 | };
62 |
63 | window.removeEventListener = (eventName, listener) => {
64 | checkEmitter();
65 | if (window.emitter.off) {
66 | window.emitter.off(eventName, listener);
67 | } else if (window.emitter.removeEventListener) {
68 | window.emitter.removeEventListener(eventName, listener);
69 | } else if (window.emitter.removeListener) {
70 | window.emitter.removeListener(eventName, listener);
71 | }
72 | };
73 |
74 | window.DOMParser = window.DOMParser || require('xmldom-qsa').DOMParser;
75 | global.TextDecoder = global.TextDecoder || TextDecoder;
76 | global.TextEncoder = global.TextEncoder || TextEncoder;
77 |
78 | const agent = 'chrome';
79 | global.userAgent = global.userAgent || agent;
80 | global.navigator.userAgent = global.navigator.userAgent || agent;
81 | global.navigator.product = 'ReactNative';
82 | global.navigator.platform = global.navigator.platform || [];
83 | global.navigator.appVersion = global.navigator.appVersion || 'OS10';
84 | global.navigator.maxTouchPoints = global.navigator.maxTouchPoints || 5;
85 | global.navigator.standalone =
86 | global.navigator.standalone === null ? true : global.navigator.standalone;
87 |
88 | window['chrome'] = window['chrome'] || {
89 | extension: {},
90 | };
91 |
92 | ///https://www.w3schools.com/js/js_window_location.asp
93 | // Newer versions of React Native define `window.location` that can't be reassigned.
94 | // When the `location` is defined, we don't have to do anything else.
95 | if ('location' in window === false) {
96 | window.location = {
97 | href: '', // window.location.href returns the href (URL) of the current page
98 | hostname: '', //window.location.hostname returns the domain name of the web host
99 | pathname: '', //window.location.pathname returns the path and filename of the current page
100 | protocol: 'https', //window.location.protocol returns the web protocol used (http: or https:)
101 | assign: null, //window.location.assign loads a new document
102 | };
103 | }
104 |
105 | if (global.document) {
106 | global.document.readyState = 'complete';
107 | }
108 |
--------------------------------------------------------------------------------