1// Copyright (C) 2023 The Android Open Source Project 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// Keep this import first. 16import '../base/static_initializers'; 17import m from 'mithril'; 18import {defer} from '../base/deferred'; 19import {reportError, addErrorHandler, ErrorDetails} from '../base/logging'; 20import {initLiveReloadIfLocalhost} from '../core/live_reload'; 21import {raf} from '../core/raf_scheduler'; 22import {setScheduleFullRedraw} from '../widgets/raf'; 23 24function getRoot() { 25 // Works out the root directory where the content should be served from 26 // e.g. `http://origin/v1.2.3/`. 27 const script = document.currentScript as HTMLScriptElement; 28 29 // Needed for DOM tests, that do not have script element. 30 if (script === null) { 31 return ''; 32 } 33 34 let root = script.src; 35 root = root.substr(0, root.lastIndexOf('/') + 1); 36 return root; 37} 38 39function setupContentSecurityPolicy() { 40 // Note: self and sha-xxx must be quoted, urls data: and blob: must not. 41 const policy = { 42 'default-src': [ 43 `'self'`, 44 ], 45 'script-src': [ 46 `'self'`, 47 ], 48 'object-src': ['none'], 49 'connect-src': [ 50 `'self'`, 51 ], 52 'img-src': [ 53 `'self'`, 54 'data:', 55 'blob:', 56 ], 57 'style-src': [ 58 `'self'`, 59 ], 60 'navigate-to': ['https://*.perfetto.dev', 'self'], 61 }; 62 const meta = document.createElement('meta'); 63 meta.httpEquiv = 'Content-Security-Policy'; 64 let policyStr = ''; 65 for (const [key, list] of Object.entries(policy)) { 66 policyStr += `${key} ${list.join(' ')}; `; 67 } 68 meta.content = policyStr; 69 document.head.appendChild(meta); 70} 71 72function main() { 73 // Wire up raf for widgets. 74 setScheduleFullRedraw(() => raf.scheduleFullRedraw()); 75 76 setupContentSecurityPolicy(); 77 78 // Load the css. The load is asynchronous and the CSS is not ready by the time 79 // appendChild returns. 80 const root = getRoot(); 81 const cssLoadPromise = defer<void>(); 82 const css = document.createElement('link'); 83 css.rel = 'stylesheet'; 84 css.href = root + 'perfetto.css'; 85 css.onload = () => cssLoadPromise.resolve(); 86 css.onerror = (err) => cssLoadPromise.reject(err); 87 const favicon = document.head.querySelector('#favicon') as HTMLLinkElement; 88 if (favicon) favicon.href = root + 'assets/favicon.png'; 89 90 document.head.append(css); 91 92 // Add Error handlers for JS error and for uncaught exceptions in promises. 93 addErrorHandler((err: ErrorDetails) => console.log(err.message, err.stack)); 94 window.addEventListener('error', (e) => reportError(e)); 95 window.addEventListener('unhandledrejection', (e) => reportError(e)); 96 97 // Prevent pinch zoom. 98 document.body.addEventListener('wheel', (e: MouseEvent) => { 99 if (e.ctrlKey) e.preventDefault(); 100 }, {passive: false}); 101 102 cssLoadPromise.then(() => onCssLoaded()); 103} 104 105function onCssLoaded() { 106 // Clear all the contents of the initial page (e.g. the <pre> error message) 107 // And replace it with the root <main> element which will be used by mithril. 108 document.body.innerHTML = ''; 109 110 raf.domRedraw = () => { 111 m.render(document.body, m('div')); 112 }; 113 114 initLiveReloadIfLocalhost(false); 115} 116 117main(); 118