diff --git a/.github/workflows/playwright-chromium-integ.yml b/.github/workflows/playwright-chromium-integ.yml index edcb94cc5..2dde8b53c 100644 --- a/.github/workflows/playwright-chromium-integ.yml +++ b/.github/workflows/playwright-chromium-integ.yml @@ -4,7 +4,7 @@ on: branches: [ main ] jobs: test: - timeout-minutes: 60 + timeout-minutes: 90 runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -16,14 +16,21 @@ jobs: run: npx playwright install --with-deps - name: Run Playwright integration tests in Chromium Attempt 1 - timeout-minutes: 30 id: attemptC1 + timeout-minutes: 20 continue-on-error: true run: npm run testIntegChromium - name: Run Playwright integration tests in Chromium Attempt 2 - if: steps.attemptC1.outcome == 'failure' id: attemptC2 + if: steps.attemptC1.outcome == 'failure' + timeout-minutes: 30 + continue-on-error: true + run: npm run testIntegChromium + + - name: Run Playwright integration tests in Chromium Attempt 3 + id: attemptC3 + if: steps.attemptC2.outcome == 'failure' run: npm run testIntegChromium - uses: actions/upload-artifact@v3 diff --git a/.github/workflows/playwright-chromium.yml b/.github/workflows/playwright-chromium.yml index 2ab4a6006..d9f667b4a 100644 --- a/.github/workflows/playwright-chromium.yml +++ b/.github/workflows/playwright-chromium.yml @@ -14,8 +14,25 @@ jobs: run: npm run build - name: Install Playwright Browsers run: npx playwright install --with-deps - - name: Run Playwright tests in chromium + + - name: Run Playwright unit tests in Chromium Attempt 1 + id: attemptC1 + timeout-minutes: 20 + continue-on-error: true run: npm run testChromium + + - name: Run Playwright unit tests in Chromium Attempt 2 + id: attemptC2 + if: steps.attemptC1.outcome == 'failure' + timeout-minutes: 30 + continue-on-error: true + run: npm run testChromium + + - name: Run Playwright unit tests in Chromium Attempt 3 + id: attemptC3 + if: steps.attemptC2.outcome == 'failure' + run: npm run testChromium + - uses: actions/upload-artifact@v3 if: always() with: diff --git a/.github/workflows/playwright-firefox-integ.yml b/.github/workflows/playwright-firefox-integ.yml index 447aad4ff..4e5ba59cf 100644 --- a/.github/workflows/playwright-firefox-integ.yml +++ b/.github/workflows/playwright-firefox-integ.yml @@ -4,7 +4,7 @@ on: branches: [ main ] jobs: test: - timeout-minutes: 60 + timeout-minutes: 90 runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -14,15 +14,23 @@ jobs: run: npm run build - name: Install Playwright Browsers run: npx playwright install --with-deps + - name: Run Playwright integration tests in firefox Attempt 1 - timeout-minutes: 30 id: attempt1 + timeout-minutes: 20 continue-on-error: true run: npm run testIntegFirefox - name: Run Playwright integration tests in firefox Attempt 2 - if: steps.attempt1.outcome == 'failure' id: attempt2 + if: steps.attempt1.outcome == 'failure' + timeout-minutes: 30 + continue-on-error: true + run: npm run testIntegFirefox + + - name: Run Playwright integration tests in firefox Attempt 3 + id: attempt3 + if: steps.attempt2.outcome == 'failure' run: npm run testIntegFirefox - uses: actions/upload-artifact@v3 diff --git a/.github/workflows/playwright-firefox.yml b/.github/workflows/playwright-firefox.yml index 07eb53994..d3a28be15 100644 --- a/.github/workflows/playwright-firefox.yml +++ b/.github/workflows/playwright-firefox.yml @@ -14,8 +14,25 @@ jobs: run: npm run build - name: Install Playwright Browsers run: npx playwright install --with-deps - - name: Run Playwright tests in firefox + + - name: Run Playwright unit tests in firefox Attempt 1 + id: attempt1 + timeout-minutes: 20 + continue-on-error: true run: npm run testFirefox + + - name: Run Playwright unit tests in firefox Attempt 2 + id: attempt2 + if: steps.attempt1.outcome == 'failure' + timeout-minutes: 30 + continue-on-error: true + run: npm run testFirefox + + - name: Run Playwright unit tests in firefox Attempt 3 + id: attempt3 + if: steps.attempt2.outcome == 'failure' + run: npm run testFirefox + - uses: actions/upload-artifact@v3 if: always() with: diff --git a/.github/workflows/playwright-on-push.yml b/.github/workflows/playwright-on-push.yml index d5b563084..cdfef2e04 100644 --- a/.github/workflows/playwright-on-push.yml +++ b/.github/workflows/playwright-on-push.yml @@ -24,11 +24,26 @@ jobs: - name: Install Playwright Browsers run: npx playwright install --with-deps - - name: Run Playwright unit tests in Firefox + - name: Run Playwright unit tests in firefox Attempt 1 + id: attempt1 + timeout-minutes: 20 + continue-on-error: true + run: npm run testFirefox + + - name: Run Playwright unit tests in firefox Attempt 2 + id: attempt2 + if: steps.attempt1.outcome == 'failure' + timeout-minutes: 30 + continue-on-error: true + run: npm run testFirefox + + - name: Run Playwright unit tests in firefox Attempt 3 + id: attempt3 + if: steps.attempt2.outcome == 'failure' run: npm run testFirefox testFirefoxInteg: - timeout-minutes: 60 + timeout-minutes: 90 runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -50,9 +65,16 @@ jobs: - name: Run Playwright integration tests in firefox Attempt 2 if: steps.attempt1.outcome == 'failure' + timeout-minutes: 30 + continue-on-error: true id: attempt2 run: npm run testIntegFirefox + - name: Run Playwright integration tests in firefox Attempt 3 + if: steps.attempt2.outcome == 'failure' + id: attempt3 + run: npm run testIntegFirefox + testChromiumUnit: timeout-minutes: 60 runs-on: ubuntu-latest @@ -68,11 +90,26 @@ jobs: - name: Install Playwright Browsers run: npx playwright install --with-deps - - name: Run Playwright unit tests in Chromium + - name: Run Playwright unit tests in Chromium Attempt 1 + id: attemptC1 + timeout-minutes: 20 + continue-on-error: true + run: npm run testChromium + + - name: Run Playwright unit tests in Chromium Attempt 2 + id: attemptC2 + if: steps.attemptC1.outcome == 'failure' + timeout-minutes: 30 + continue-on-error: true + run: npm run testChromium + + - name: Run Playwright unit tests in Chromium Attempt 3 + id: attemptC3 + if: steps.attemptC2.outcome == 'failure' run: npm run testChromium testChromiumInteg: - timeout-minutes: 60 + timeout-minutes: 90 runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -93,10 +130,17 @@ jobs: run: npm run testIntegChromium - name: Run Playwright integration tests in Chromium Attempt 2 + timeout-minutes: 30 + continue-on-error: true if: steps.attemptC1.outcome == 'failure' id: attemptC2 run: npm run testIntegChromium + - name: Run Playwright integration tests in Chromium Attempt 3 + if: steps.attemptC2.outcome == 'failure' + id: attemptC3 + run: npm run testIntegChromium + raiseIssue: needs: [ testFirefoxUnit, testFirefoxInteg, testChromiumUnit, testChromiumInteg] runs-on: ubuntu-latest diff --git a/src-node/package.json b/src-node/package.json index 6c36618b6..aa664a75b 100644 --- a/src-node/package.json +++ b/src-node/package.json @@ -8,6 +8,7 @@ "homepage": "https://github.com/phcode-dev/phoenix", "license": "GNU-AGPL3.0", "scripts": { + "_watch_src-node": "cd .. && npm run _watch_src-node" }, "engines": { "node": "18" diff --git a/src/assets/new-project/assets/js/code-editor.js b/src/assets/new-project/assets/js/code-editor.js index 685eafdcf..435524e56 100644 --- a/src/assets/new-project/assets/js/code-editor.js +++ b/src/assets/new-project/assets/js/code-editor.js @@ -77,15 +77,13 @@ function getDisplayLocation(projectPath) { return Strings.PROJECT_FROM_BROWSER; } -const DEFAULT_PROJECT_PATH = '/fs/local/default project'; - function _updateProjectCards() { let recentProjectList = $(document.getElementById('recentProjectList')); recentProjectList.empty(); let recentProjects = recentProjectExtension.getRecentProjects(); let tabIndex = 1; - let defaultProjects = [DEFAULT_PROJECT_PATH, '/fs/local/explore'], - omitProjectsInListing = ['/fs/local/explore'], + let defaultProjects = [newProjectExtension.getWelcomeProjectPath(), newProjectExtension.getExploreProjectPath()], + omitProjectsInListing = [newProjectExtension.getExploreProjectPath()], showRecentProjects = false; for(let recentProject of recentProjects){ if(!defaultProjects.includes(recentProject)){ diff --git a/src/assets/new-project/assets/js/new-project-more.js b/src/assets/new-project/assets/js/new-project-more.js index 051b9c24c..969f0fffc 100644 --- a/src/assets/new-project/assets/js/new-project-more.js +++ b/src/assets/new-project/assets/js/new-project-more.js @@ -30,7 +30,8 @@ function _getIconURL(iconURL) { return 'images/Bootstrap_logo.svg'; } else if(iconURL === 'appLogo'){ return 'images/logo.png'; - } else if(iconURL && (iconURL.startsWith("https://") || iconURL.startsWith("http://") || iconURL.startsWith("tauri://"))){ + } else if(iconURL && (iconURL.startsWith("https://") || iconURL.startsWith("http://") + || iconURL.startsWith("tauri://") || iconURL.startsWith("asset://"))){ return iconURL; } return 'images/tab-img2.png'; // HTML icon diff --git a/src/extensibility/Package.js b/src/extensibility/Package.js index 23ef7afab..491c3b06c 100644 --- a/src/extensibility/Package.js +++ b/src/extensibility/Package.js @@ -176,7 +176,7 @@ define(function (require, exports, module) { ExtensionLoader.loadExtension(result.name, { // On Windows, it looks like Node converts Unix-y paths to backslashy paths. // We need to convert them back. - baseUrl: window.fsServerUrl.slice(0, -1) + result.installedTo + baseUrl: window.Phoenix.VFS.getVirtualServingURLForPath(result.installedTo) }, "main").then(function () { d.resolve(result); }, function () { @@ -241,11 +241,12 @@ define(function (require, exports, module) { d.reject(Errors.MALFORMED_URL); return d.promise(); } - if (!(parsed.protocol === "http:" || parsed.protocol === "https:" || parsed.protocol === "tauri:")) { + if (!(parsed.protocol === "http:" || parsed.protocol === "https:" + || parsed.protocol === "tauri:" || parsed.protocol === "asset:")) { d.reject(Errors.UNSUPPORTED_PROTOCOL); return d.promise(); } - + parsed.filename = FileUtils.convertWindowsPathToUnixPath(parsed.filename); const urlInfo = { url: url, parsed: parsed, filenameHint: parsed.filename, destinationDirectory }; githubURLFilter(urlInfo); diff --git a/src/extensions/default/DebugCommands/main.js b/src/extensions/default/DebugCommands/main.js index 3d2590b4b..01dd525cc 100644 --- a/src/extensions/default/DebugCommands/main.js +++ b/src/extensions/default/DebugCommands/main.js @@ -715,7 +715,11 @@ define(function (require, exports, module) { } function _openVirtualServer() { - Phoenix.app.openURLInPhoenixWindow(window.fsServerUrl, { + const virtualServingURL = Phoenix.VFS.getVirtualServingURLForPath("/"); + if(!virtualServingURL) { + throw new Error("Unable to find virtual server!"); + } + Phoenix.app.openURLInPhoenixWindow(virtualServingURL, { preferTabs: true }); } @@ -775,13 +779,20 @@ define(function (require, exports, module) { menu.addMenuItem(DEBUG_LIVE_PREVIEW_LOGGING); menu.addMenuDivider(); menu.addMenuItem(DEBUG_OPEN_VFS); - menu.addMenuItem(DEBUG_OPEN_VIRTUAL_SERVER); + menu.addMenuItem(DEBUG_OPEN_VIRTUAL_SERVER, undefined, undefined, undefined, { + hideWhenCommandDisabled: true + }); menu.addMenuDivider(); menu.addMenuItem(DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW); // this command will enable defaultPreferences and brackets preferences to be open side by side in split view. menu.addMenuItem(Commands.FILE_OPEN_KEYMAP); // this command is defined in core, but exposed only in Debug menu for now CommandManager.get(DEBUG_UNLOAD_CURRENT_EXTENSION) .setEnabled(extensionDevelopment.isProjectLoadedAsExtension()); + if(window.__TAURI__) { + // in tauri, virtual server doesnt exist, extensions are served by tauri asset urls. + CommandManager.get(DEBUG_OPEN_VIRTUAL_SERVER) + .setEnabled(false); + } _updateLogToConsoleMenuItemChecked(); // exposed for convenience, but not official API exports._runUnitTests = _runUnitTests; diff --git a/src/extensions/default/HTMLCodeHints/HTMLJumpToDef.js b/src/extensions/default/HTMLCodeHints/HTMLJumpToDef.js index 8b5a8e957..71de6b2b0 100644 --- a/src/extensions/default/HTMLCodeHints/HTMLJumpToDef.js +++ b/src/extensions/default/HTMLCodeHints/HTMLJumpToDef.js @@ -58,7 +58,8 @@ define(function (require, exports, module) { }; function _openFile(fileRelativePath, mainDocPath) { - if(fileRelativePath.startsWith("http://") || fileRelativePath.startsWith("https://")){ + if(fileRelativePath.startsWith("http://") || fileRelativePath.startsWith("https://") + || fileRelativePath.startsWith("tauri://") || fileRelativePath.startsWith("asset://")){ return FileViewController.openAndSelectDocument(fileRelativePath, FileViewController.PROJECT_MANAGER); } const targetPath = path.resolve(mainDocPath, fileRelativePath); diff --git a/src/extensions/default/Phoenix-live-preview/main.js b/src/extensions/default/Phoenix-live-preview/main.js index 8759d6082..4305711e6 100644 --- a/src/extensions/default/Phoenix-live-preview/main.js +++ b/src/extensions/default/Phoenix-live-preview/main.js @@ -66,7 +66,7 @@ define(function (require, exports, module) { `; diff --git a/src/extensions/default/Phoenix-live-preview/panel.html b/src/extensions/default/Phoenix-live-preview/panel.html index 000fe7170..a16af77b0 100644 --- a/src/extensions/default/Phoenix-live-preview/panel.html +++ b/src/extensions/default/Phoenix-live-preview/panel.html @@ -18,7 +18,7 @@
diff --git a/src/extensions/default/Phoenix/newly-added-features.js b/src/extensions/default/Phoenix/newly-added-features.js index 1ede11221..296af3957 100644 --- a/src/extensions/default/Phoenix/newly-added-features.js +++ b/src/extensions/default/Phoenix/newly-added-features.js @@ -93,7 +93,9 @@ define(function (require, exports, module) { if(!Phoenix.firstBoot && !window.testEnvironment){ _showNewUpdatesIfPresent(); } - Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM, "cache", "doRefresh"); - window.refreshServiceWorkerCache(_cacheUpdatedCB); + if(!Phoenix.browser.isTauri) { + Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM, "cache", "doRefresh"); + window.refreshServiceWorkerCache(_cacheUpdatedCB); + } }; }); diff --git a/src/extensions/default/QuickView/ImagePreviewProvider.js b/src/extensions/default/QuickView/ImagePreviewProvider.js index d8372ee9d..f4eafaab1 100644 --- a/src/extensions/default/QuickView/ImagePreviewProvider.js +++ b/src/extensions/default/QuickView/ImagePreviewProvider.js @@ -41,7 +41,7 @@ define(function (require, exports, module) { extensionlessImagePreview; // Whether to try and preview extensionless URLs // List of protocols which we will support for image preview urls - let validProtocols = ["data:", "http:", "https:", "tauri:", "ftp:", "file:"]; + let validProtocols = ["data:", "http:", "https:", "tauri:", "asset:", "ftp:", "file:"]; prefs = PreferencesManager.getExtensionPrefs("quickview"); diff --git a/src/extensions/default/RecentProjects/main.js b/src/extensions/default/RecentProjects/main.js index 627d19ada..27db65f17 100644 --- a/src/extensions/default/RecentProjects/main.js +++ b/src/extensions/default/RecentProjects/main.js @@ -19,6 +19,8 @@ * */ +/*global Phoenix*/ + define(function (require, exports, module) { @@ -347,23 +349,17 @@ define(function (require, exports, module) { /** * Parses the path and returns an object with the full path, the folder name and the path without the folder. - * @param {string} path The full path to the folder. + * @param {string} fullPath The full path to the folder. * @return {{path: string, folder: string, rest: string}} */ - function parsePath(path) { - var lastSlash = path.lastIndexOf("/"), folder, rest; - if (lastSlash === path.length - 1) { - lastSlash = path.slice(0, path.length - 1).lastIndexOf("/"); - } - if (lastSlash >= 0) { - rest = " - " + (lastSlash ? path.slice(0, lastSlash) : "/"); - folder = path.slice(lastSlash + 1); - } else { - rest = "/"; - folder = path; + function renderPath(fullPath) { + let parentDirPath = window.path.dirname(fullPath); + if(parentDirPath.startsWith(Phoenix.VFS.getTauriDir())) { + parentDirPath = window.fs.getTauriPlatformPath(parentDirPath); } + const rest = " - " + parentDirPath; - return {path: path, folder: folder, rest: rest}; + return {path: fullPath, folder: window.path.basename(fullPath), rest: rest}; } /** @@ -382,7 +378,7 @@ define(function (require, exports, module) { recentProjects.forEach(function (root) { if (root !== currentProject) { - templateVars.projectList.push(parsePath(root)); + templateVars.projectList.push(renderPath(root)); } }); diff --git a/src/extensions/default/RemoteFileAdapter/main.js b/src/extensions/default/RemoteFileAdapter/main.js index f82f54ee3..d124daa63 100644 --- a/src/extensions/default/RemoteFileAdapter/main.js +++ b/src/extensions/default/RemoteFileAdapter/main.js @@ -32,9 +32,10 @@ define(function (require, exports, module) { MainViewManager = brackets.getModule("view/MainViewManager"), Menus = brackets.getModule("command/Menus"); - var HTTP_PROTOCOL = "http:", + const HTTP_PROTOCOL = "http:", HTTPS_PROTOCOL = "https:", - TAURI_PROTOCOL = "tauri:"; + TAURI_PROTOCOL = "tauri:", + TAURI_ASSET_PROTOCOL = "asset:"; ExtensionUtils.loadStyleSheet(module, "styles.css"); @@ -51,6 +52,10 @@ define(function (require, exports, module) { return "tauri"; } + if (data.fullPath.startsWith("asset://")) { + return "asset"; + } + return ""; } @@ -129,7 +134,7 @@ define(function (require, exports, module) { }, match: function (query) { var protocol = PathUtils.parseUrl(query).protocol; - return [HTTP_PROTOCOL, HTTPS_PROTOCOL, TAURI_PROTOCOL].indexOf(protocol) !== -1; + return [HTTP_PROTOCOL, HTTPS_PROTOCOL, TAURI_PROTOCOL, TAURI_ASSET_PROTOCOL].indexOf(protocol) !== -1; }, itemFocus: function (query) { // no op diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 66df955d9..41b3c8f23 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -1229,7 +1229,8 @@ define(function (require, exports, module) { // attach remote file handlers var HTTP_PROTOCOL = "http:", HTTPS_PROTOCOL = "https:", - TAURI_PROTOCOL = "tauri:"; + TAURI_PROTOCOL = "tauri:", + TAURI_ASSET_PROTOCOL = "asset:"; var protocolAdapter = { priority: 0, // Default priority @@ -1243,4 +1244,5 @@ define(function (require, exports, module) { registerProtocolAdapter(HTTP_PROTOCOL, protocolAdapter); registerProtocolAdapter(HTTPS_PROTOCOL, protocolAdapter); registerProtocolAdapter(TAURI_PROTOCOL, protocolAdapter); + registerProtocolAdapter(TAURI_ASSET_PROTOCOL, protocolAdapter); }); diff --git a/src/index.html b/src/index.html index ce6e4248a..40d4c80cd 100644 --- a/src/index.html +++ b/src/index.html @@ -26,9 +26,12 @@ + content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: asset: https://asset.localhost localhost:* ws://localhost:* https://storage.googleapis.com https://platform.twitter.com https://buttons.github.io https://unpkg.com/@aicore/ https://www.googletagmanager.com; + img-src * data: localhost:* asset: https://asset.localhost ; + media-src * data: localhost:* asset: https://asset.localhost ; + font-src * data: localhost:* asset: https://asset.localhost ; + frame-src * localhost:* asset: https://asset.localhost ; + connect-src * localhost:* asset: https://asset.localhost ;"> @@ -39,8 +42,32 @@ Phoenix Code @@ -68,6 +95,19 @@ return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && window['safari'].pushNotification)); } + function detectEngine() { + const userAgent = navigator.userAgent; + + if (/WebKit/.test(userAgent)) { + if (/Chrome|Blink|Edg\//.test(userAgent)) { + return 'Blink'; // Chrome or Edge (based on Blink) + } else { + return 'WebKit'; // Likely Safari or other true WebKit-based browser + } + } + + return 'Other'; + } function getBrowserDetails() { let isChrome = navigator.userAgent.indexOf("Chrome") !== -1; let isEdgeBrowser = navigator.userAgent.indexOf("Edg") !== -1; @@ -95,7 +135,9 @@ isChrome: isChromeBrowser, isEdgeChromium: isEdgeChromiumBrowser, isOperaChromium: isOperaChromiumBrowser, - isSafari: isSafari() + isSafari: isSafari(), + isWebKit: detectEngine() === 'WebKit', + isBlink: detectEngine() === 'Blink' } }; } diff --git a/src/loggerSetup.js b/src/loggerSetup.js index f43a7f639..20d80afca 100644 --- a/src/loggerSetup.js +++ b/src/loggerSetup.js @@ -138,20 +138,20 @@ logger.loggingOptions.LOCAL_STORAGE_KEYS.LOG_LIVE_PREVIEW); function _shouldDiscardError(errors = []) { - if(!window.fsServerUrl || !window.Phoenix || !window.Phoenix.VFS){ + if(!window.Phoenix || !window.Phoenix.VFS){ return false; } let fileURL, extensionName, userFsURLFound = false, - userExtensionsURL = window.fsServerUrl.slice(0, -1) + window.Phoenix.VFS.getUserExtensionDir() + "/"; + userExtensionsFolderURL = window.Phoenix.VFS.getVirtualServingURLForPath(window.Phoenix.VFS.getUserExtensionDir()+"/"); // errors with stacks originating from any folder or files from the user file system are not logged for privacy for(let error of errors){ if(error.stacktrace && error.stacktrace[0]) { for(let stack of error.stacktrace){ fileURL = stack.file || ""; - if(fileURL.startsWith(userExtensionsURL)) { + if(fileURL.startsWith(userExtensionsFolderURL)) { // an extension installed from extension store has error. we dont log, but raise metric - extensionName = fileURL.replace(userExtensionsURL, ""); + extensionName = fileURL.replace(userExtensionsFolderURL, ""); extensionName = extensionName.split("/")[0]; let supportStatus = "Y"; if(!Phoenix.isSupportedBrowser){ @@ -165,7 +165,7 @@ `Extension Error for ${extensionName} of type ${error.type} class ${error.errorClass}`); return true; } - if(fileURL.startsWith(window.fsServerUrl)) { + if(window.Phoenix.VFS.getPathForVirtualServingURL(fileURL)) { userFsURLFound = true; } } @@ -249,6 +249,8 @@ browser = 'operaLegacy'; } else if(Phoenix.browser.desktop.isSafari){ browser = 'safari'; + } else if(Phoenix.browser.desktop.isWebKit){ + browser = 'webkit'; } context = `${context}-${Phoenix.platform}-${browser}`; } diff --git a/src/phoenix/init_vfs.js b/src/phoenix/init_vfs.js index b343fcc0b..5a1ec3c9f 100644 --- a/src/phoenix/init_vfs.js +++ b/src/phoenix/init_vfs.js @@ -31,22 +31,29 @@ * This module should be functionally as light weight as possible with minimal deps as it is a shell component. * **/ -const EXTENSION_DIR = '/fs/app/extensions/'; +let extensionDIR, + appSupportDIR, + tauriAssetServeDir, + tauriAssetServeBaseURL, + documentsDIR, + tempDIR, + userProjectsDir; function _setupVFS(fsLib, pathLib){ Phoenix.VFS = { getRootDir: () => '/fs/', getMountDir: () => '/mnt/', getTauriDir: () => '/tauri/', - getAppSupportDir: () => '/fs/app/', - getExtensionDir: () => EXTENSION_DIR, - getUserExtensionDir: () => `${EXTENSION_DIR}user`, - getDevExtensionDir: () => `${EXTENSION_DIR}dev`, - getLocalDir: () => '/fs/local/', - getTempDir: () => '/temp/', - getTrashDir: () => '/fs/trash/', - getDefaultProjectDir: () => '/fs/local/default project/', - getUserDocumentsDirectory: () => '/fs/local/Documents/', + getAppSupportDir: () => appSupportDIR, + getExtensionDir: () => extensionDIR, + getUserExtensionDir: () => `${extensionDIR}user`, + getDevExtensionDir: () => `${extensionDIR}dev`, + getTempDir: () => tempDIR, + getTauriAssetServeDir: () => tauriAssetServeDir, + getUserDocumentsDirectory: () => documentsDIR, + getUserProjectsDirectory: () => userProjectsDir, + _getVirtualDocumentsDirectory: () => '/fs/local/', + getDefaultProjectDir: () => `${userProjectsDir}default project/`, /** * Check if a given full path is located in the users local machine drive. For eg. fs access paths are accounted * as local disc path, as well as tauri fs paths. @@ -80,15 +87,34 @@ function _setupVFS(fsLib, pathLib){ * Converts a phoenix virtual serving url to absolute path in file system or null * http://localhost:8000/src/phoenix/vfs/fs/app/extensions/user/themesforbrackets/requirejs-config.json * to /fs/app/extensions/user/themesforbrackets/requirejs-config.json - * @param fullPath + * @param fullURL * @returns {string|null} */ - getPathForVirtualServingURL: function (fullPath) { - if(window.fsServerUrl && fullPath.startsWith(window.fsServerUrl)){ - return fullPath.replace(window.fsServerUrl, "/"); + getPathForVirtualServingURL: function (fullURL) { + if(Phoenix.browser.isTauri) { + if(fullURL.startsWith(tauriAssetServeBaseURL)){ + const assetRelativePath = decodeURIComponent(fullURL.replace(tauriAssetServeBaseURL, "")) + .replace(/\\/g, "/"); // replace windows path forward slashes \ to / + return `${tauriAssetServeDir}${assetRelativePath}`; + } + return null; + } + if(window.fsServerUrl && fullURL.startsWith(window.fsServerUrl)){ + return fullURL.replace(window.fsServerUrl, "/"); } return null; }, + getVirtualServingURLForPath: function (fullPath) { + if(Phoenix.browser.isTauri) { + if(fullPath.startsWith(tauriAssetServeDir)){ + const platformPath = fs.getTauriPlatformPath(fullPath) + .replace(/\\/g, "/"); // windows style paths to unix style c:\x\y to c:/x/y + return decodeURIComponent(window.__TAURI__.tauri.convertFileSrc(platformPath)); + } + return null; + } + return window.fsServerUrl.slice(0, -1) + fullPath; + }, ensureExistsDirAsync: async function (path) { return new Promise((resolve, reject)=>{ Phoenix.VFS.ensureExistsDir(path, (err) =>{ @@ -187,18 +213,69 @@ function _tryCreateDefaultProject() { }); } -const _createAppDirs = function () { - // Create phoenix app dirs - return Promise.all([ +async function setupAppSupportAndExtensionsDir() { + if(Phoenix.browser.isTauri) { + appSupportDIR = fs.getTauriVirtualPath(window._tauriBootVars.appLocalDir); + if(!appSupportDIR.endsWith("/")){ + appSupportDIR = `${appSupportDIR}/`; + } + tauriAssetServeDir = `${appSupportDIR}assets/`; + tauriAssetServeBaseURL = decodeURIComponent(window.__TAURI__.tauri.convertFileSrc( + fs.getTauriPlatformPath(tauriAssetServeDir))) + .replace(/\\/g, "/"); // windows style paths to unix style c:\x\y to c:/x/y + extensionDIR = `${tauriAssetServeDir}extensions/`; + } else { + appSupportDIR = '/fs/app/'; + extensionDIR = `${appSupportDIR}extensions/`; + } + await Promise.all([ Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getRootDir()), Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getAppSupportDir()), - Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getLocalDir()), - Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getTrashDir()), - Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getTempDir()), Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getExtensionDir()), - Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getExtensionDir()+"user"), - Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getExtensionDir()+"dev"), - _tryCreateDefaultProject() + Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getUserExtensionDir()), + Phoenix.VFS.ensureExistsDirAsync(Phoenix.VFS.getDevExtensionDir()) + ]); +} + +async function setupDocumentsDir() { + if(Phoenix.browser.isTauri) { + documentsDIR = fs.getTauriVirtualPath(window._tauriBootVars.documentDir); + if(!documentsDIR.endsWith("/")){ + documentsDIR = `${documentsDIR}/`; + } + const appName = window._tauriBootVars.appname; + userProjectsDir = `${documentsDIR}${appName}/`; + } else { + documentsDIR = Phoenix.VFS._getVirtualDocumentsDirectory(); + userProjectsDir = documentsDIR; + } + await Phoenix.VFS.ensureExistsDirAsync(documentsDIR); + await _tryCreateDefaultProject(); +} + +async function setupTempDir() { + if(Phoenix.browser.isTauri) { + tempDIR = fs.getTauriVirtualPath(window._tauriBootVars.tempDir); + if(!tempDIR.endsWith("/")){ + tempDIR = `${tempDIR}/`; + } + const appName = window._tauriBootVars.appname; + tempDIR = `${tempDIR}${appName}/`; + } else { + tempDIR = '/temp/'; + } + await Phoenix.VFS.ensureExistsDirAsync(tempDIR); +} + +const _createAppDirs = async function () { + if(window._tauriBootVarsPromise) { + await window._tauriBootVarsPromise; + } + // Create phoenix app dirs + await Promise.all([ + setupAppSupportAndExtensionsDir(), + setupDocumentsDir(), + setupTempDir() ]); }; diff --git a/src/phoenix/shell.js b/src/phoenix/shell.js index 89cb1882d..0d3cb39e8 100644 --- a/src/phoenix/shell.js +++ b/src/phoenix/shell.js @@ -97,7 +97,10 @@ Phoenix.app = { return nativeWindow; }, getApplicationSupportDirectory: Phoenix.VFS.getAppSupportDir, + getExtensionsDirectory: Phoenix.VFS.getExtensionDir, getUserDocumentsDirectory: Phoenix.VFS.getUserDocumentsDirectory, + getUserProjectsDirectory: Phoenix.VFS.getUserProjectsDirectory, + getTempDirectory: Phoenix.VFS.getTempDir, ERR_CODES: ERR_CODES, getElapsedMilliseconds: function () { return Date.now() - Phoenix.startTime; // milliseconds elapsed since app start diff --git a/src/phoenix/virtual-server-loader.js b/src/phoenix/virtual-server-loader.js index 89ae98f50..c225ce743 100644 --- a/src/phoenix/virtual-server-loader.js +++ b/src/phoenix/virtual-server-loader.js @@ -56,7 +56,9 @@ function getRoute(){ return `phoenix/vfs`; } -window.fsServerUrl = _getBaseURL() + getRoute() + "/"; +if(!window.__TAURI__) { + window.fsServerUrl = _getBaseURL() + getRoute() + "/"; +} function _isServiceWorkerLoaderPage() { // only http(s)://x.y.z/ or http(s)://x.y.z/index.html can load service worker, or localhost/src for dev builds diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 940efd5c5..35293c2f7 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -762,7 +762,7 @@ define(function (require, exports, module) { * @returns {string} */ function getLocalProjectsPath() { - return Phoenix.VFS.getLocalDir(); + return Phoenix.VFS.getUserProjectsDirectory(); } /** @@ -1008,16 +1008,15 @@ define(function (require, exports, module) { // close all the old files MainViewManager._closeAll(MainViewManager.ALL_PANES); - _unwatchProjectRoot().always(function () { - // Done closing old project (if any) - if (model.projectRoot) { - LanguageManager._resetPathLanguageOverrides(); - PreferencesManager._reloadUserPrefs(model.projectRoot); - exports.trigger(EVENT_PROJECT_CLOSE, model.projectRoot); - } + _unwatchProjectRoot().fail(console.error); - startLoad.resolve(); - }); + if (model.projectRoot) { + LanguageManager._resetPathLanguageOverrides(); + PreferencesManager._reloadUserPrefs(model.projectRoot); + exports.trigger(EVENT_PROJECT_CLOSE, model.projectRoot); + } + + startLoad.resolve(); } startLoad.done(function () { @@ -1237,7 +1236,7 @@ define(function (require, exports, module) { */ function deleteItem(entry) { var result = new $.Deferred(); - let name = _getProjectRelativePathForCopy(entry.fullPath); + let name = _getProjectDisplayNameOrPath(entry.fullPath); let message = StringUtils.format(Strings.DELETING, name); setProjectBusy(true, message); entry.unlink(function (err) { @@ -1436,7 +1435,7 @@ define(function (require, exports, module) { fullPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE); } if(fullPath && isWithinProject(fullPath)){ - let name = _getProjectRelativePathForCopy(fullPath); + let name = _getProjectDisplayNameOrPath(fullPath); let message = StringUtils.format(Strings.DUPLICATING, name); setProjectBusy(true, message); FileSystem.getFreePath(fullPath, (err, dupePath)=>{ @@ -1444,7 +1443,7 @@ define(function (require, exports, module) { setProjectBusy(false, message); if(err){ _showErrorDialog(ERR_TYPE_DUPLICATE_FAILED, false, "err", - _getProjectRelativePathForCopy(fullPath)); + _getProjectDisplayNameOrPath(fullPath)); return; } FileSystem.resolve(dupePath, function (err, file) { @@ -1468,7 +1467,7 @@ define(function (require, exports, module) { function _zipFailed(fullPath) { _showErrorDialog(ERR_TYPE_DOWNLOAD_FAILED, false, "err", - _getProjectRelativePathForCopy(fullPath)); + _getProjectDisplayNameOrPath(fullPath)); } function _downloadFolderCommand(downloadPath) { @@ -1499,7 +1498,7 @@ define(function (require, exports, module) { _zipFailed(fullPath); return; } - let name = _getProjectRelativePathForCopy(fullPath); + let name = _getProjectDisplayNameOrPath(fullPath); let message = StringUtils.format(Strings.DOWNLOADING_FILE, name); if(fileOrFolder.isFile){ setProjectBusy(true, message); @@ -1551,18 +1550,20 @@ define(function (require, exports, module) { return relativePath; } - function _getProjectRelativePathForCopy(path) { + function _getProjectDisplayNameOrPath(path) { // sometimes, when we copy across projects, there can be two project roots at work. For eg, when copying // across /mnt/prj1 and /app/local/prj2; both should correctly resolve to prj1/ and prj2/ even though only // /mnt/prj1 is the current active project root. So we cannot really use getProjectRoot().fullPath for all cases let projectRootParent = window.path.dirname(getProjectRoot().fullPath); - let relativePath = window.path.relative(projectRootParent, path); + let displayPath = window.path.relative(projectRootParent, path); if(path.startsWith(Phoenix.VFS.getMountDir())){ - relativePath = window.path.relative(Phoenix.VFS.getMountDir(), path); - } else if(path.startsWith(Phoenix.VFS.getLocalDir())){ - relativePath = window.path.relative(Phoenix.VFS.getLocalDir(), path); + displayPath = window.path.relative(Phoenix.VFS.getMountDir(), path); + } else if(path.startsWith(Phoenix.VFS.getTauriDir())){ + displayPath = window.fs.getTauriPlatformPath(path); + } else if(path.startsWith(Phoenix.VFS._getVirtualDocumentsDirectory())){ + displayPath = window.path.relative(Phoenix.VFS._getVirtualDocumentsDirectory(), path); } - return relativePath; + return displayPath; } function _copyProjectRelativePath() { @@ -1627,15 +1628,15 @@ define(function (require, exports, module) { async function _validatePasteTarget(srcEntry, targetEntry) { if(_isSubPathOf(srcEntry.fullPath, targetEntry.fullPath)){ _showErrorDialog(ERR_TYPE_PASTE_FAILED, srcEntry.isDirectory, "err", - _getProjectRelativePathForCopy(srcEntry.fullPath), - _getProjectRelativePathForCopy(targetEntry.fullPath)); + _getProjectDisplayNameOrPath(srcEntry.fullPath), + _getProjectDisplayNameOrPath(targetEntry.fullPath)); return false; } let baseName = window.path.basename(srcEntry.fullPath); let targetPath = window.path.normalize(`${targetEntry.fullPath}/${baseName}`); let exists = await FileSystem.existsAsync(targetPath); if(exists){ - _showErrorDialog(ERR_TYPE_PASTE, srcEntry.isDirectory, "err", _getProjectRelativePathForCopy(targetPath)); + _showErrorDialog(ERR_TYPE_PASTE, srcEntry.isDirectory, "err", _getProjectDisplayNameOrPath(targetPath)); return false; } return true; @@ -1648,14 +1649,14 @@ define(function (require, exports, module) { if(canPaste){ let baseName = window.path.basename(srcEntry.fullPath); let targetPath = window.path.normalize(`${target.fullPath}/${baseName}`); - let message = StringUtils.format(Strings.MOVING, _getProjectRelativePathForCopy(srcEntry.fullPath)); + let message = StringUtils.format(Strings.MOVING, _getProjectDisplayNameOrPath(srcEntry.fullPath)); setProjectBusy(true, message); srcEntry.rename(targetPath, (err)=>{ setProjectBusy(false, message); if(err){ _showErrorDialog(ERR_TYPE_PASTE_FAILED, srcEntry.isDirectory, "err", - _getProjectRelativePathForCopy(srcEntry.fullPath), - _getProjectRelativePathForCopy(target.fullPath)); + _getProjectDisplayNameOrPath(srcEntry.fullPath), + _getProjectDisplayNameOrPath(target.fullPath)); return; } queuePathForSelection = targetPath; @@ -1668,15 +1669,15 @@ define(function (require, exports, module) { let srcEntry = (await FileSystem.resolveAsync(src)).entry; let canPaste = await _validatePasteTarget(srcEntry, target); if(canPaste){ - let name = _getProjectRelativePathForCopy(srcEntry.fullPath); + let name = _getProjectDisplayNameOrPath(srcEntry.fullPath); let message = StringUtils.format(Strings.COPYING, name); setProjectBusy(true, message); FileSystem.copy(srcEntry.fullPath, target.fullPath, (err, targetStat)=>{ setProjectBusy(false, message); if(err){ _showErrorDialog(ERR_TYPE_PASTE_FAILED, srcEntry.isDirectory, "err", - _getProjectRelativePathForCopy(srcEntry.fullPath), - _getProjectRelativePathForCopy(target.fullPath)); + _getProjectDisplayNameOrPath(srcEntry.fullPath), + _getProjectDisplayNameOrPath(target.fullPath)); return; } queuePathForSelection = targetStat.realPath; diff --git a/src/utils/ExtensionLoader.js b/src/utils/ExtensionLoader.js index 5ad8b9728..d74c57208 100644 --- a/src/utils/ExtensionLoader.js +++ b/src/utils/ExtensionLoader.js @@ -220,7 +220,17 @@ define(function (require, exports, module) { baseUrl: config.baseUrl, paths: globalPaths, locale: brackets.getLocale(), - waitSeconds: EXTENSION_LOAD_TIMOUT_SECONDS + waitSeconds: EXTENSION_LOAD_TIMOUT_SECONDS, + config: { + text: { + useXhr: function(_url, _protocol, _hostname, _port) { + // as we load extensions in cross domain fashion, we have to use xhr + // https://github.com/requirejs/text#xhr-restrictions + // else user installed extension require will fail in tauri + return true; + } + } + } }; const isDefaultExtensionModule =( extensionConfig.baseUrl && extensionConfig.baseUrl.startsWith(`${window.PhoenixBaseURL}extensions/default/`)); @@ -379,7 +389,8 @@ define(function (require, exports, module) { function testExtension(name, config, entryPoint) { var result = new $.Deferred(), extensionPath = config.baseUrl + "/" + entryPoint + ".js"; - if(extensionPath.startsWith("http://") || extensionPath.startsWith("https://") || extensionPath.startsWith("tauri://")) { + if(extensionPath.startsWith("http://") || extensionPath.startsWith("https://") + || extensionPath.startsWith("tauri://") || extensionPath.startsWith("asset://")) { return _testExtensionByURL(name, config, entryPoint); } @@ -407,14 +418,13 @@ define(function (require, exports, module) { * @private * Loads a file entryPoint from each extension folder within the baseUrl into its own Require.js context * - * @param {!string} directory, an absolute native path that contains a directory of extensions. + * @param {!string} directory an absolute native path that contains a directory of extensions. * each subdirectory is interpreted as an independent extension - * @param {!{baseUrl: string}} config object with baseUrl property containing absolute path of extension folder * @param {!string} entryPoint Module name to load (without .js suffix) * @param {function} processExtension * @return {!$.Promise} A promise object that is resolved when all extensions complete loading. */ - function _loadAll(directory, config, entryPoint, processExtension) { + function _loadAll(directory, entryPoint, processExtension) { var result = new $.Deferred(); FileSystem.getDirectoryForPath(directory).getContents(function (err, contents) { @@ -437,10 +447,9 @@ define(function (require, exports, module) { Async.doInParallel(extensions, function (item) { var extConfig = { - // we load extensions in virtual file system from our virtual server URL - // fsServerUrl always ends with a / - baseUrl: window.fsServerUrl.slice(0, -1) + config.baseUrl + "/" + item, - paths: config.paths + // we load user installed extensions in file system from our virtual/asset server URL + baseUrl: Phoenix.VFS.getVirtualServingURLForPath(directory + "/" + item), + paths: {} }; console.log("Loading Extension from virtual fs: ", extConfig); return processExtension(item, extConfig, entryPoint); @@ -489,18 +498,18 @@ define(function (require, exports, module) { * @return {!$.Promise} A promise object that is resolved when all extensions complete loading. */ function loadAllExtensionsInNativeDirectory(directory) { - return _loadAll(directory, {baseUrl: directory}, "main", loadExtension); + return _loadAll(directory, "main", loadExtension); } /** - * Loads a given extension at the path from virtual fs. + * Loads a given extension at the path from virtual fs. Used by `debug menu> load project as extension` * @param directory * @return {!Promise} */ function loadExtensionFromNativeDirectory(directory) { logger.leaveTrail("loading custom extension from path: " + directory); const extConfig = { - baseUrl: window.fsServerUrl.slice(0, -1) + directory.replace(/\/$/, "") + baseUrl: Phoenix.VFS.getVirtualServingURLForPath(directory.replace(/\/$/, "")) }; return loadExtension("ext" + directory.replace("/", "-"), // /fs/user/extpath to ext-fs-user-extpath extConfig, 'main'); @@ -514,21 +523,20 @@ define(function (require, exports, module) { * @return {!$.Promise} A promise object that is resolved when all extensions complete loading. */ function testAllExtensionsInNativeDirectory(directory) { - var result = new $.Deferred(); - var virtualServerURL = window.fsServerUrl, - extensionsDir = _getExtensionPath() + "/" + directory, + const result = new $.Deferred(); + const extensionsDir = _getExtensionPath() + "/" + directory, config = { - baseUrl: virtualServerURL + extensionsDir + baseUrl: Phoenix.VFS.getVirtualServingURLForPath(extensionsDir) }; config.paths = { - "perf": virtualServerURL + "/test/perf", - "spec": virtualServerURL + "/test/spec" + "perf": Phoenix.VFS.getVirtualServingURLForPath( "/test/perf"), + "spec": Phoenix.VFS.getVirtualServingURLForPath("/test/spec") }; FileSystem.getDirectoryForPath(extensionsDir).getContents(function (err, contents) { if (!err) { - var i, + let i, extensions = []; for (i = 0; i < contents.length; i++) { diff --git a/src/view/ThemeManager.js b/src/view/ThemeManager.js index 41cd67d70..6bf0611e8 100644 --- a/src/view/ThemeManager.js +++ b/src/view/ThemeManager.js @@ -380,9 +380,12 @@ define(function (require, exports, module) { function _loadFileFromURL(url, options) { let deferred = new $.Deferred(); - const themeName = options.name || options.theme.title, - fileName = options.theme.file || (typeof(options.theme) === 'string'? options.theme: `theme.css`), - themeFolder = brackets.app.getApplicationSupportDirectory() + `/extensions/user/${themeName}/`, + const themeName = options.name || options.theme.title, + themeFolder = window.__TAURI__ ? + brackets.app.getApplicationSupportDirectory() + `/assets/extensions/user/${themeName}/` : + brackets.app.getApplicationSupportDirectory() + `/extensions/user/${themeName}/`; + + const fileName = options.theme.file || (typeof(options.theme) === 'string'? options.theme: `theme.css`), packageURL = url.substring(0, url.lastIndexOf("/")) + '/package.json', packagePath = path.normalize(themeFolder + 'package.json'), themePath = path.normalize(themeFolder + fileName), @@ -438,7 +441,8 @@ define(function (require, exports, module) { * @return {$.Promise} promise object resolved with the theme to be loaded from fileName */ function loadFile(fileName, options) { - if(fileName.startsWith("http://") || fileName.startsWith("https://") || fileName.startsWith("tauri://")) { + if(fileName.startsWith("http://") || fileName.startsWith("https://") + || fileName.startsWith("tauri://") || fileName.startsWith("asset://")) { if(Phoenix.VFS.getPathForVirtualServingURL(fileName)){ fileName = Phoenix.VFS.getPathForVirtualServingURL(fileName); } else { diff --git a/test/SpecRunner.html b/test/SpecRunner.html index 35b81507c..72e345fb2 100644 --- a/test/SpecRunner.html +++ b/test/SpecRunner.html @@ -24,9 +24,12 @@ Jasmine Spec Runner + content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: asset: https://asset.localhost localhost:* ws://localhost:* https://storage.googleapis.com https://platform.twitter.com https://buttons.github.io https://unpkg.com/@aicore/ https://www.googletagmanager.com; + img-src * data: localhost:* asset: https://asset.localhost ; + media-src * data: localhost:* asset: https://asset.localhost ; + font-src * data: localhost:* asset: https://asset.localhost ; + frame-src * localhost:* asset: https://asset.localhost ; + connect-src * localhost:* asset: https://asset.localhost ;"> @@ -64,6 +86,20 @@ (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); return check; } + + function detectEngine() { + const userAgent = navigator.userAgent; + + if (/WebKit/.test(userAgent)) { + if (/Chrome|Blink|Edg\//.test(userAgent)) { + return 'Blink'; // Chrome or Edge (based on Blink) + } else { + return 'WebKit'; // Likely Safari or other true WebKit-based browser + } + } + + return 'Other'; + } function getBrowserDetails() { let isChrome = navigator.userAgent.indexOf("Chrome") !== -1; let isEdgeBrowser = navigator.userAgent.indexOf("Edg") !== -1; @@ -98,7 +134,9 @@ isChrome: isChromeBrowser, isEdgeChromium: isEdgeChromiumBrowser, isOperaChromium: isOperaChromiumBrowser, - isSafari: isSafari() + isSafari: isSafari(), + isWebKit: detectEngine() === 'WebKit', + isBlink: detectEngine() === 'Blink' } }; } @@ -236,6 +274,10 @@ height: 100%; } + #phoenixIframeContainer.hovered { + opacity: 0.5; + } + #phoenixControlBar{ display: flex; justify-content: end; @@ -254,7 +296,7 @@ let container = document.getElementById('phoenixIframeContainer'); container.classList.remove("forced-hidden"); - container.appendChild(iframe); + container.insertBefore(iframe, container.firstChild); return iframe; } function closeIframeRunner() { @@ -275,7 +317,7 @@ } iframe.className = "phoenixIframe"; - container.appendChild(iframe); + container.insertBefore(iframe, container.firstChild); } function _disposeIframeRunner() { @@ -286,10 +328,25 @@ iframe.remove(); }); } + + function init() { + const controlBar = document.getElementById("phoenixControlBar"); + const iframeContainer = document.getElementById("phoenixIframeContainer"); + + controlBar.addEventListener('mouseover', function() { + console.log("here") + iframeContainer.classList.add('hovered'); + }); + + controlBar.addEventListener('mouseout', function() { + iframeContainer.classList.remove('hovered'); + }); + } + - +