mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
14 Commits
v40.0.0-be
...
v35.0.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91bb748eaa | ||
|
|
d77c2d75ed | ||
|
|
f4c3eb4391 | ||
|
|
9aca9e9fb6 | ||
|
|
e0fa647601 | ||
|
|
6b0fd02c0a | ||
|
|
1017ac821f | ||
|
|
91b53b633a | ||
|
|
8da9572592 | ||
|
|
291bbff5d8 | ||
|
|
d704a3fc5b | ||
|
|
fb70b81ee6 | ||
|
|
97fa059e1f | ||
|
|
8a64cdc0b1 |
@@ -21,7 +21,6 @@ an issue:
|
||||
### Getting started
|
||||
|
||||
* [Introduction](tutorial/introduction.md)
|
||||
* [Quick Start](tutorial/quick-start.md)
|
||||
* [Process Model](tutorial/process-model.md)
|
||||
|
||||
### Learning the basics
|
||||
|
||||
@@ -14,7 +14,7 @@ To configure a local keyboard shortcut, you need to specify an [`accelerator`][]
|
||||
property when creating a [MenuItem][] within the [Menu][] module.
|
||||
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), update the `main.js` to be:
|
||||
[tutorial starter code][tutorial-starter-code], update the `main.js` to be:
|
||||
|
||||
```fiddle docs/fiddles/features/keyboard-shortcuts/local
|
||||
const { app, BrowserWindow, Menu, MenuItem } = require('electron/main')
|
||||
@@ -75,7 +75,7 @@ module to detect keyboard events even when the application does not have
|
||||
keyboard focus.
|
||||
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), update the `main.js` to be:
|
||||
[tutorial starter code][tutorial-starter-code], update the `main.js` to be:
|
||||
|
||||
```fiddle docs/fiddles/features/keyboard-shortcuts/global
|
||||
const { app, BrowserWindow, globalShortcut } = require('electron/main')
|
||||
@@ -144,7 +144,7 @@ is emitted before dispatching `keydown` and `keyup` events in the page. It can
|
||||
be used to catch and handle custom shortcuts that are not visible in the menu.
|
||||
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), update the `main.js` file with the
|
||||
[tutorial starter code][tutorial-starter-code], update the `main.js` file with the
|
||||
following lines:
|
||||
|
||||
```fiddle docs/fiddles/features/keyboard-shortcuts/interception-from-main
|
||||
@@ -207,3 +207,4 @@ Mousetrap.bind('up up down down left right left right b a enter', () => {
|
||||
[mousetrap]: https://github.com/ccampbell/mousetrap
|
||||
[dom-events]: https://developer.mozilla.org/en-US/docs/Web/Events
|
||||
[addEventListener-api]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
|
||||
[tutorial-starter-code]: tutorial-2-first-app.md#final-starter-code
|
||||
|
||||
@@ -86,7 +86,7 @@ The main process also controls your application's lifecycle through Electron's
|
||||
that you can use to add custom application behavior (for instance, programmatically
|
||||
quitting your application, modifying the application dock, or showing an About panel).
|
||||
|
||||
As a practical example, the app shown in the [quick start guide][quick-start-lifecycle]
|
||||
As a practical example, the app shown in the [tutorial starter code][tutorial-lifecycle]
|
||||
uses `app` APIs to create a more native application window experience.
|
||||
|
||||
```js title='main.js'
|
||||
@@ -97,7 +97,7 @@ app.on('window-all-closed', () => {
|
||||
```
|
||||
|
||||
[app]: ../api/app.md
|
||||
[quick-start-lifecycle]: ../tutorial/quick-start.md#manage-your-windows-lifecycle
|
||||
[tutorial-lifecycle]: ../tutorial/tutorial-2-first-app.md#quit-the-app-when-all-windows-are-closed-windows--linux
|
||||
|
||||
### Native APIs
|
||||
|
||||
|
||||
@@ -1,513 +0,0 @@
|
||||
# Quick Start
|
||||
|
||||
This guide will step you through the process of creating a barebones Hello World app in
|
||||
Electron, similar to [`electron/electron-quick-start`][quick-start].
|
||||
|
||||
By the end of this tutorial, your app will open a browser window that displays a web page
|
||||
with information about which Chromium, Node.js, and Electron versions are running.
|
||||
|
||||
[quick-start]: https://github.com/electron/electron-quick-start
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To use Electron, you need to install [Node.js][node-download]. We recommend that you
|
||||
use the latest `LTS` version available.
|
||||
|
||||
> Please install Node.js using pre-built installers for your platform.
|
||||
> You may encounter incompatibility issues with different development tools otherwise.
|
||||
|
||||
To check that Node.js was installed correctly, type the following commands in your
|
||||
terminal client:
|
||||
|
||||
```sh
|
||||
node -v
|
||||
npm -v
|
||||
```
|
||||
|
||||
The commands should print the versions of Node.js and npm accordingly.
|
||||
|
||||
**Note:** Since Electron embeds Node.js into its binary, the version of Node.js running
|
||||
your code is unrelated to the version running on your system.
|
||||
|
||||
[node-download]: https://nodejs.org/en/download/
|
||||
|
||||
## Create your application
|
||||
|
||||
### Scaffold the project
|
||||
|
||||
Electron apps follow the same general structure as other Node.js projects.
|
||||
Start by creating a folder and initializing an npm package.
|
||||
|
||||
```sh npm2yarn
|
||||
mkdir my-electron-app && cd my-electron-app
|
||||
npm init
|
||||
```
|
||||
|
||||
The interactive `init` command will prompt you to set some fields in your config.
|
||||
There are a few rules to follow for the purposes of this tutorial:
|
||||
|
||||
* `entry point` should be `main.js`.
|
||||
* `author` and `description` can be any value, but are necessary for
|
||||
[app packaging](#package-and-distribute-your-application).
|
||||
|
||||
Your `package.json` file should look something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-electron-app",
|
||||
"version": "1.0.0",
|
||||
"description": "Hello World!",
|
||||
"main": "main.js",
|
||||
"author": "Jane Doe",
|
||||
"license": "MIT"
|
||||
}
|
||||
```
|
||||
|
||||
Then, install the `electron` package into your app's `devDependencies`.
|
||||
|
||||
```sh npm2yarn
|
||||
npm install --save-dev electron
|
||||
```
|
||||
|
||||
> Note: If you're encountering any issues with installing Electron, please
|
||||
> refer to the [Advanced Installation][advanced-installation] guide.
|
||||
|
||||
Finally, you want to be able to execute Electron. In the [`scripts`][package-scripts]
|
||||
field of your `package.json` config, add a `start` command like so:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"start": "electron ."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This `start` command will let you open your app in development mode.
|
||||
|
||||
```sh npm2yarn
|
||||
npm start
|
||||
```
|
||||
|
||||
> Note: This script tells Electron to run on your project's root folder. At this stage,
|
||||
> your app will immediately throw an error telling you that it cannot find an app to run.
|
||||
|
||||
[advanced-installation]: ./installation.md
|
||||
[package-scripts]: https://docs.npmjs.com/cli/v7/using-npm/scripts
|
||||
|
||||
### Run the main process
|
||||
|
||||
The entry point of any Electron application is its `main` script. This script controls the
|
||||
**main process**, which runs in a full Node.js environment and is responsible for
|
||||
controlling your app's lifecycle, displaying native interfaces, performing privileged
|
||||
operations, and managing renderer processes (more on that later).
|
||||
|
||||
During execution, Electron will look for this script in the [`main`][package-json-main]
|
||||
field of the app's `package.json` config, which you should have configured during the
|
||||
[app scaffolding](#scaffold-the-project) step.
|
||||
|
||||
To initialize the `main` script, create an empty file named `main.js` in the root folder
|
||||
of your project.
|
||||
|
||||
> Note: If you run the `start` script again at this point, your app will no longer throw
|
||||
> any errors! However, it won't do anything yet because we haven't added any code into
|
||||
> `main.js`.
|
||||
|
||||
[package-json-main]: https://docs.npmjs.com/cli/v7/configuring-npm/package-json#main
|
||||
|
||||
### Create a web page
|
||||
|
||||
Before we can create a window for our application, we need to create the content that
|
||||
will be loaded into it. In Electron, each window displays web contents that can be loaded
|
||||
from either a local HTML file or a remote URL.
|
||||
|
||||
For this tutorial, you will be doing the former. Create an `index.html` file in the root
|
||||
folder of your project:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||
<title>Hello World!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
We are using Node.js <span id="node-version"></span>,
|
||||
Chromium <span id="chrome-version"></span>,
|
||||
and Electron <span id="electron-version"></span>.
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
> Note: Looking at this HTML document, you can observe that the version numbers are
|
||||
> missing from the body text. We'll manually insert them later using JavaScript.
|
||||
|
||||
### Opening your web page in a browser window
|
||||
|
||||
Now that you have a web page, load it into an application window. To do so, you'll
|
||||
need two Electron modules:
|
||||
|
||||
* The [`app`][app] module, which controls your application's event lifecycle.
|
||||
* The [`BrowserWindow`][browser-window] module, which creates and manages application
|
||||
windows.
|
||||
|
||||
Because the main process runs Node.js, you can import these as [CommonJS][commonjs]
|
||||
modules at the top of your `main.js` file:
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
```
|
||||
|
||||
Then, add a `createWindow()` function that loads `index.html` into a new `BrowserWindow`
|
||||
instance.
|
||||
|
||||
```js
|
||||
const createWindow = () => {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
```
|
||||
|
||||
Next, call this `createWindow()` function to open your window.
|
||||
|
||||
In Electron, browser windows can only be created after the `app` module's
|
||||
[`ready`][app-ready] event is fired. You can wait for this event by using the
|
||||
[`app.whenReady()`][app-when-ready] API. Call `createWindow()` after `whenReady()`
|
||||
resolves its Promise.
|
||||
|
||||
```js @ts-type={createWindow:()=>void}
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
})
|
||||
```
|
||||
|
||||
> Note: At this point, your Electron application should successfully
|
||||
> open a window that displays your web page!
|
||||
|
||||
[app]: ../api/app.md
|
||||
[browser-window]: ../api/browser-window.md
|
||||
[commonjs]: https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules
|
||||
[app-ready]: ../api/app.md#event-ready
|
||||
[app-when-ready]: ../api/app.md#appwhenready
|
||||
|
||||
### Manage your window's lifecycle
|
||||
|
||||
Although you can now open a browser window, you'll need some additional boilerplate code
|
||||
to make it feel more native to each platform. Application windows behave differently on
|
||||
each OS, and Electron puts the responsibility on developers to implement these
|
||||
conventions in their app.
|
||||
|
||||
In general, you can use the `process` global's [`platform`][node-platform] attribute
|
||||
to run code specifically for certain operating systems.
|
||||
|
||||
#### Quit the app when all windows are closed (Windows & Linux)
|
||||
|
||||
On Windows and Linux, exiting all windows generally quits an application entirely.
|
||||
|
||||
To implement this, listen for the `app` module's [`'window-all-closed'`][window-all-closed]
|
||||
event, and call [`app.quit()`][app-quit] if the user is not on macOS (`darwin`).
|
||||
|
||||
```js
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
```
|
||||
|
||||
[node-platform]: https://nodejs.org/api/process.html#process_process_platform
|
||||
[window-all-closed]: ../api/app.md#event-window-all-closed
|
||||
[app-quit]: ../api/app.md#appquit
|
||||
|
||||
#### Open a window if none are open (macOS)
|
||||
|
||||
Whereas Linux and Windows apps quit when they have no windows open, macOS apps generally
|
||||
continue running even without any windows open, and activating the app when no windows
|
||||
are available should open a new one.
|
||||
|
||||
To implement this feature, listen for the `app` module's [`activate`][activate]
|
||||
event, and call your existing `createWindow()` method if no browser windows are open.
|
||||
|
||||
Because windows cannot be created before the `ready` event, you should only listen for
|
||||
`activate` events after your app is initialized. Do this by attaching your event listener
|
||||
from within your existing `whenReady()` callback.
|
||||
|
||||
[activate]: ../api/app.md#event-activate-macos
|
||||
|
||||
```js @ts-type={createWindow:()=>void}
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
> Note: At this point, your window controls should be fully functional!
|
||||
|
||||
### Access Node.js from the renderer with a preload script
|
||||
|
||||
Now, the last thing to do is print out the version numbers for Electron and its
|
||||
dependencies onto your web page.
|
||||
|
||||
Accessing this information is trivial to do in the main process through Node's
|
||||
global `process` object. However, you can't just edit the DOM from the main
|
||||
process because it has no access to the renderer's `document` context.
|
||||
They're in entirely different processes!
|
||||
|
||||
> Note: If you need a more in-depth look at Electron processes, see the
|
||||
> [Process Model][] document.
|
||||
|
||||
This is where attaching a **preload** script to your renderer comes in handy.
|
||||
A preload script runs before the renderer process is loaded, and has access to both
|
||||
renderer globals (e.g. `window` and `document`) and a Node.js environment.
|
||||
|
||||
Create a new script named `preload.js` as such:
|
||||
|
||||
```js
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const replaceText = (selector, text) => {
|
||||
const element = document.getElementById(selector)
|
||||
if (element) element.innerText = text
|
||||
}
|
||||
|
||||
for (const dependency of ['chrome', 'node', 'electron']) {
|
||||
replaceText(`${dependency}-version`, process.versions[dependency])
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The above code accesses the Node.js `process.versions` object and runs a basic `replaceText`
|
||||
helper function to insert the version numbers into the HTML document.
|
||||
|
||||
To attach this script to your renderer process, pass in the path to your preload script
|
||||
to the `webPreferences.preload` option in your existing `BrowserWindow` constructor.
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
// include the Node.js 'path' module at the top of your file
|
||||
const path = require('node:path')
|
||||
|
||||
// modify your existing createWindow() function
|
||||
const createWindow = () => {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
// ...
|
||||
```
|
||||
|
||||
There are two Node.js concepts that are used here:
|
||||
|
||||
* The [`__dirname`][dirname] string points to the path of the currently executing script
|
||||
(in this case, your project's root folder).
|
||||
* The [`path.join`][path-join] API joins multiple path segments together, creating a
|
||||
combined path string that works across all platforms.
|
||||
|
||||
We use a path relative to the currently executing JavaScript file so that your relative
|
||||
path will work in both development and packaged mode.
|
||||
|
||||
[Process Model]: ./process-model.md
|
||||
[dirname]: https://nodejs.org/api/modules.html#modules_dirname
|
||||
[path-join]: https://nodejs.org/api/path.html#path_path_join_paths
|
||||
|
||||
### Bonus: Add functionality to your web contents
|
||||
|
||||
At this point, you might be wondering how to add more functionality to your application.
|
||||
|
||||
For any interactions with your web contents, you want to add scripts to your
|
||||
renderer process. Because the renderer runs in a normal web environment, you can add a
|
||||
`<script>` tag right before your `index.html` file's closing `</body>` tag to include
|
||||
any arbitrary scripts you want:
|
||||
|
||||
```html
|
||||
<script src="./renderer.js"></script>
|
||||
```
|
||||
|
||||
The code contained in `renderer.js` can then use the same JavaScript APIs and tooling
|
||||
you use for typical front-end development, such as using [`webpack`][webpack] to bundle
|
||||
and minify your code or [React][react] to manage your user interfaces.
|
||||
|
||||
[webpack]: https://webpack.js.org
|
||||
[react]: https://reactjs.org
|
||||
|
||||
### Recap
|
||||
|
||||
After following the above steps, you should have a fully functional
|
||||
Electron application that looks like this:
|
||||
|
||||

|
||||
|
||||
<!--TODO(erickzhao): Remove the individual code blocks for static website -->
|
||||
The full code is available below:
|
||||
|
||||
```js
|
||||
// main.js
|
||||
|
||||
// Modules to control application life and create native browser window
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const path = require('node:path')
|
||||
|
||||
const createWindow = () => {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
})
|
||||
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
// Open the DevTools.
|
||||
// mainWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and require them here.
|
||||
```
|
||||
|
||||
```js
|
||||
// preload.js
|
||||
|
||||
// All the Node.js APIs are available in the preload process.
|
||||
// It has the same sandbox as a Chrome extension.
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const replaceText = (selector, text) => {
|
||||
const element = document.getElementById(selector)
|
||||
if (element) element.innerText = text
|
||||
}
|
||||
|
||||
for (const dependency of ['chrome', 'node', 'electron']) {
|
||||
replaceText(`${dependency}-version`, process.versions[dependency])
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<!--index.html-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||
<title>Hello World!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
We are using Node.js <span id="node-version"></span>,
|
||||
Chromium <span id="chrome-version"></span>,
|
||||
and Electron <span id="electron-version"></span>.
|
||||
|
||||
<!-- You can also require other files to run in this process -->
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```fiddle docs/fiddles/quick-start
|
||||
```
|
||||
|
||||
To summarize all the steps we've done:
|
||||
|
||||
* We bootstrapped a Node.js application and added Electron as a dependency.
|
||||
* We created a `main.js` script that runs our main process, which controls our app
|
||||
and runs in a Node.js environment. In this script, we used Electron's `app` and
|
||||
`BrowserWindow` modules to create a browser window that displays web content
|
||||
in a separate process (the renderer).
|
||||
* In order to access certain Node.js functionality in the renderer, we attached
|
||||
a preload script to our `BrowserWindow` constructor.
|
||||
|
||||
## Package and distribute your application
|
||||
|
||||
The fastest way to distribute your newly created app is using
|
||||
[Electron Forge](https://www.electronforge.io).
|
||||
|
||||
:::info
|
||||
|
||||
To build an RPM package for Linux, you will need to [install its required system dependencies](https://www.electronforge.io/config/makers/rpm).
|
||||
|
||||
:::
|
||||
|
||||
1. Add a description to your `package.json` file, otherwise rpmbuild will fail. Blank description are not valid.
|
||||
2. Add Electron Forge as a development dependency of your app, and use its `import` command to set up
|
||||
Forge's scaffolding:
|
||||
|
||||
```sh npm2yarn
|
||||
npm install --save-dev @electron-forge/cli
|
||||
npx electron-forge import
|
||||
|
||||
✔ Checking your system
|
||||
✔ Initializing Git Repository
|
||||
✔ Writing modified package.json file
|
||||
✔ Installing dependencies
|
||||
✔ Writing modified package.json file
|
||||
✔ Fixing .gitignore
|
||||
|
||||
We have ATTEMPTED to convert your app to be in a format that electron-forge understands.
|
||||
|
||||
Thanks for using "electron-forge"!!!
|
||||
```
|
||||
|
||||
3. Create a distributable using Forge's `make` command:
|
||||
|
||||
```sh npm2yarn
|
||||
npm run make
|
||||
|
||||
> my-electron-app@1.0.0 make /my-electron-app
|
||||
> electron-forge make
|
||||
|
||||
✔ Checking your system
|
||||
✔ Resolving Forge Config
|
||||
We need to package your application before we can make it
|
||||
✔ Preparing to Package Application for arch: x64
|
||||
✔ Preparing native dependencies
|
||||
✔ Packaging Application
|
||||
Making for the following targets: zip
|
||||
✔ Making for target: zip - On platform: darwin - For arch: x64
|
||||
```
|
||||
|
||||
Electron Forge creates the `out` folder where your package will be located:
|
||||
|
||||
```plain
|
||||
// Example for macOS
|
||||
out/
|
||||
├── out/make/zip/darwin/x64/my-electron-app-darwin-x64-1.0.0.zip
|
||||
├── ...
|
||||
└── out/my-electron-app-darwin-x64/my-electron-app.app/Contents/MacOS/my-electron-app
|
||||
```
|
||||
@@ -1,3 +1,5 @@
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Window Customization
|
||||
|
||||
The [`BrowserWindow`][] module is the foundation of your Electron application, and
|
||||
@@ -5,13 +7,15 @@ it exposes many APIs that let you customize the look and behavior of your app’
|
||||
This section covers how to implement various use cases for window customization on macOS,
|
||||
Windows, and Linux.
|
||||
|
||||
:::info
|
||||
`BrowserWindow` is a subclass of the [`BaseWindow`][] module. Both modules allow
|
||||
you to create and manage application windows in Electron, with the main difference
|
||||
being that `BrowserWindow` supports a single, full size web view while `BaseWindow`
|
||||
supports composing many web views. `BaseWindow` can be used interchangeably with `BrowserWindow`
|
||||
in the examples of the documents in this section.
|
||||
:::
|
||||
> [!NOTE]
|
||||
> `BrowserWindow` is a subclass of the [`BaseWindow`][] module. Both modules allow
|
||||
> you to create and manage application windows in Electron, with the main difference
|
||||
> being that `BrowserWindow` supports a single, full size web view while `BaseWindow`
|
||||
> supports composing many web views. `BaseWindow` can be used interchangeably with `BrowserWindow`
|
||||
> in the examples of the documents in this section.
|
||||
|
||||
<!-- markdownlint-disable-next-line MD033 -->
|
||||
<DocCardList />
|
||||
|
||||
[`BaseWindow`]: ../api/base-window.md
|
||||
[`BrowserWindow`]: ../api/browser-window.md
|
||||
|
||||
@@ -8,7 +8,7 @@ If your app doesn't use any native modules, then it's really easy to create an A
|
||||
|
||||
1. Make sure that your app's `node_modules` directory is empty.
|
||||
2. Using a _Command Prompt_, run `set npm_config_arch=arm64` before running `npm install`/`yarn install` as usual.
|
||||
3. [If you have Electron installed as a development dependency](quick-start.md#prerequisites), npm will download and unpack the arm64 version. You can then package and distribute your app as normal.
|
||||
3. [If you have Electron installed as a development dependency](tutorial-2-first-app.md#initializing-your-npm-project), npm will download and unpack the arm64 version. You can then package and distribute your app as normal.
|
||||
|
||||
## General considerations
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ To set user tasks for your application, you can use
|
||||
##### Set user tasks
|
||||
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), update the `main.js` file with the
|
||||
[tutorial starter code][tutorial-starter-code], update the `main.js` file with the
|
||||
following lines:
|
||||
|
||||
```js
|
||||
@@ -121,7 +121,7 @@ To set thumbnail toolbar in your application, you need to use
|
||||
##### Set thumbnail toolbar
|
||||
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), update the `main.js` file with the
|
||||
[tutorial starter code][tutorial-starter-code], update the `main.js` file with the
|
||||
following lines:
|
||||
|
||||
```js
|
||||
@@ -185,7 +185,7 @@ To set the overlay icon for a window, you need to use the
|
||||
#### Example
|
||||
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), update the `main.js` file with the
|
||||
[tutorial starter code][tutorial-starter-code], update the `main.js` file with the
|
||||
following lines:
|
||||
|
||||
```js
|
||||
@@ -214,7 +214,7 @@ To flash the BrowserWindow taskbar button, you need to use the
|
||||
#### Example
|
||||
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), update the `main.js` file with the
|
||||
[tutorial starter code][tutorial-starter-code], update the `main.js` file with the
|
||||
following lines:
|
||||
|
||||
```js
|
||||
@@ -231,10 +231,10 @@ In the above example, it is called when the window comes into focus,
|
||||
but you might use a timeout or some other event to disable it.
|
||||
|
||||
[msdn-flash-frame]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-flashwindow#remarks
|
||||
|
||||
[setthumbarbuttons]: ../api/browser-window.md#winsetthumbarbuttonsbuttons-windows
|
||||
[setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows
|
||||
[setoverlayicon]: ../api/browser-window.md#winsetoverlayiconoverlay-description-windows
|
||||
[flashframe]: ../api/browser-window.md#winflashframeflag
|
||||
[recent-documents]: ./recent-documents.md
|
||||
[progress-bar]: ./progress-bar.md
|
||||
[tutorial-starter-code]: ../tutorial/tutorial-2-first-app.md#final-starter-code
|
||||
|
||||
103
docs/why-electron.md
Normal file
103
docs/why-electron.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Why Electron
|
||||
|
||||
Electron is a framework enabling developers to build cross-platform desktop applications for macOS, Windows, and Linux by combining web technologies (HTML, JavaScript, CSS) with Node.js and native code. It is open-source, MIT-licensed, and free for both commercial and personal use. In this document, we’ll explain why companies and developers choose Electron.
|
||||
|
||||
We can split up the benefits of Electron in two questions: First, why should you use web technologies to build your application? Then, why should you choose Electron as the framework to do so?
|
||||
|
||||
If you’re already using web technologies for your application, you can skip straight to the `Why Electron?` section below.
|
||||
|
||||
## Why choose web technologies
|
||||
|
||||
Web technologies include HTML, CSS, JavaScript, and WebAssembly. They’re the storefront of the modern Internet. Those technologies have emerged as the best choice for building user interfaces — both for consumer applications as well as mission-critical business applications. This is true both for applications that need to run in a browser as well as desktop applications that are not accessible from a browser. Our bold claim here is that this isn’t just true for cross-platform applications that need to run on multiple operating systems but true overall.
|
||||
|
||||
As an example, NASA’s actual [Mission Control](https://github.com/nasa/openmct) is written with web technologies. The [Bloomberg Terminal](https://en.wikipedia.org/wiki/Bloomberg_Terminal), the computer system found at every financial institution, is written with web technologies and runs inside Chromium. It costs $25,000 per user, per year. The McDonald’s ordering kiosk, powering the world’s biggest food retailer, is entirely built with Chromium. The [SpaceX’s Dragon 2 space capsule](https://old.reddit.com/r/spacex/comments/gxb7j1/we_are_the_spacex_software_team_ask_us_anything/ft62781/?context=3) uses Chromium to display its interface. You get the point: web technologies are a great tech stack to build user interfaces.
|
||||
|
||||
Here are the reasons we, the Electron maintainers, are betting on the web.
|
||||
|
||||
### Versatility
|
||||
|
||||
Modern versions of HTML and CSS enable your developers and designers to fully express themselves. The web’s showcase includes Google Earth, Netflix, Spotify, Gmail, Facebook, Airbnb, or GitHub. Whatever interface your application needs, you will be able to express it with HTML, CSS, and JavaScript.
|
||||
|
||||
If you want to focus on building a great product without figuring out how you can realize your designer’s vision in a specific UI framework, the web is a safe bet.
|
||||
|
||||
### Reliability
|
||||
|
||||
Web technologies are the most-used foundation for user interfaces on the planet. The have been hardened accordingly. Modern computers have been optimized from the CPU to the operating system to be good at running web technologies. The manufacturers of your user’s devices—be that an Android phone or the latest MacBook—will ensure that they can visit websites, play videos on YouTube, or display emails. In turn, they’ll also ensure that your app has a stable foundation, even if you have just one user.
|
||||
|
||||
If you want to focus on building a great product without debugging a weird quirk that nobody has found before, the web is a safe bet.
|
||||
|
||||
### Interoperability
|
||||
|
||||
Whatever provider or customer data you need to interact with, they will have probably thought of an integration path with the web. Depending on your technology choice, embedding a YouTube video either takes 30 seconds or requires you to hire a team devoted to streaming and hardware-accelerated video decoding. In the case of YouTube, using anything other than the provided players is actually against their terms and conditions, so you’ll likely embed a browser frame before you implement your own video streaming decoder.
|
||||
|
||||
There will be virtually no platform where your app cannot run if you build it with web technologies. Virtually all devices with a display—be that an ATM, a car infotainment system, a smart TV, a fridge, or a Nintendo Switch—come with means to display web technologies. The web is safe bet if you want to be cross-platform.
|
||||
|
||||
### Ubiquity
|
||||
|
||||
It’s easy to find developers with experience building with web technologies. If you’re a developer, it’ll be easy to find answers to your questions on Google, Stack Overflow, GitHub, or a coding AI of your choice. Whatever problem you need to solve, it’s likely that somebody has solved it well before—and that you can find the answer to the puzzle online.
|
||||
|
||||
If you want to focus on building a great product with ample access to resources and materials, the web is a safe bet.
|
||||
|
||||
## Why choose Electron
|
||||
|
||||
Electron combines Chromium, Node.js, and the ability to write custom native code into one framework for building powerful desktop applications. There are three main reasons to use Electron:
|
||||
|
||||
### Enterprise-grade
|
||||
|
||||
Electron is reliable, secure, stable, and mature. It is the premier choice for companies building their flagship product. We have a list of some of those companies on our homepage, but just among chat apps, Slack, Discord, and Skype are built with Electron. Among AI applications, both OpenAI’s ChatGPT and Anthropic’s Claude use Electron. Visual Studio Code, Loom, Canva, Notion, Docker, and countless other leading developers of software bet on Electron.
|
||||
|
||||
We did make it a priority to make Electron easy to work with and a delight for developers. That’s likely the main reason why Electron became as popular as it is today — but what keeps Electron alive and thriving is the maintainer’s focus on making Electron as stable, secure, performant, and capable of mission-critical use cases for end users as possible. We’re building an Electron that is ready to be used in scenarios where unfixable bugs, unpatched security holes, and outages of any kind are worst-case scenarios.
|
||||
|
||||
### Mature
|
||||
|
||||
Our current estimation is that most desktop computers on the planet run at least one Electron app. Electron has grown by prioritizing talent in its maintainer group, fostering excellent and sustainable engineering practices in managing the ongoing maintenance, and proactively inviting companies betting on Electron to directly contribute to the project. We’re an impact project with the OpenJS foundation, which is itself a part of the Linux foundation. We share resources and expertise with other foundation projects like Node.js, ESLint, Webpack - or the Linux Kernel or Kubernetes.
|
||||
|
||||
What does all of that mean for you, a developer, in practice?
|
||||
|
||||
- **Reliable release schedule**: Electron will release a new major version in lockstep with every second major Chromium release, usually on the same day as Chromium. A lot of work, both in the form of building processes and tools, but also in terms of raw invested hours every week, has to go into making that happen.
|
||||
- **No dictators**: Sometimes, betting on a technology also requires you to bet on a single person or company. In turn, it requires you to trust that the person or company never has a breakdown, starts fighting you directly, or does anything else drastic that’ll force you rethink your entire tech stack. Electron is maintained by a diverse set of companies (Microsoft, Slack/Salesforce, Notion, and more) and will continue to welcome more companies interested in ensuring their “seat at the decision-making table”.
|
||||
|
||||
### Stability, security, performance
|
||||
|
||||
Electron delivers the best experience on all target platforms (macOS, Windows, Linux) by bundling the latest version of Chromium, V8, and Node.js directly with the application binary. When it comes to running and rendering web content with upmost stability, security, and performance, we currently believe that stack to be “best in class”.
|
||||
|
||||
#### Why bundle anything at all
|
||||
|
||||
You might wonder why we bundle Chromium’s web stack with our apps when most modern operating systems already ship a browser and some form of web view. Bundling doesn’t just increase the amount of work for Electron maintainers dramatically, it also increases the total disk size of Electron apps (most apps are >100MB). Many Electron maintainers once developed applications that did make use of embedded web views — and have since accepted the increased disk size and maintainer work as a worthy trade-off.
|
||||
|
||||
When using an operating system's built-in web view, you're limited by the browser version included in the oldest operating system version you need to support. We have found the following problems with this approach:
|
||||
|
||||
- **Stability**: The modern web technology stack is complex, and as a result, you’ll sooner or later encounter bugs. If you use the operating system’s web view, your only recourse will be to ask your customers to upgrade their operating system. If no upgrade is available for that machine (because of no ability to upgrade to the latest macOS or Windows 11), you’ll have to ask them to buy a new computer. If you’re unlucky, you’re now losing a major customer because they will not upgrade their entire fleet of thousands of machines just because one team wanted to try your startup’s app. You have _no recourse_ in this situation. Even the risk of that happening is unacceptable to the companies that employ the Electron maintainers.
|
||||
- **Security:** Similar to how you can fix stability bugs by releasing an app update, you can also release security fixes to your application without asking your customer to upgrade their operating system. Even if operating system providers prioritize updates to their built-in browser, we have not seen them reliably update the built-in web views with similar urgency. Bundling a web renderer gives you, the developer, control.
|
||||
- **Performance:** For simple HTML documents, a built-in web view will sometimes use fewer resources than an app with a bundled framework. For bigger apps, it is our experience that we can deliver better performance with the latest version of Chromium than we can with built-in web views. You might think that the built-in view can share a lot of resources with other apps and the operating system— but for security reasons, apps have to run in their own sandboxes, isolated from each other. At that point, the question is whether the OS’ web view is more performant than Chromium. Across many apps, our experience is that bundling Chromium and Node.js enables us to build better and more performant experiences.
|
||||
|
||||
#### Why bundle Chromium and Node.js
|
||||
|
||||
Electron aims to enable the apps it supports to deliver the best possible user experience, followed by the best possible developer experience. Chromium is currently the best cross-platform rendering stack available. Node.js uses Chromium’s JavaScript engine V8, allowing us to combine the powers of both.
|
||||
|
||||
- **Native code when you want it**: Thanks to Node.js’ mature native addon system, you can always write native code. There is no system API out of reach for you. Whatever macOS, Windows, or Linux feature you’ll want to integrate with —as long as you can do it in C, C++, Objective-C, Rust, or another native language, you’ll be able to do it in Electron. Again, this gives you, the developer, maximum control. With Electron, you can use web technologies without choosing _only_ web technologies.
|
||||
|
||||
### Developer experience
|
||||
|
||||
To summarize, we aim to build an Electron that is mature, enterprise-grade, and ready for mission-critical applications. We prioritize reliability, stability, security, and performance. That said, you might also choose Electron for its developer experience:
|
||||
|
||||
- **Powerful ecosystem**: Anything you find on npm will run inside Electron. Any resource available to you about how to work with Node.js also applies to Electron. In addition, Electron itself has a [thriving ecosystem](https://www.npmjs.com/search?q=electron) — including plenty of choices for installers, updaters, deeper operating system-integration, and more.
|
||||
- **Plenty of built-in capabilities:** Over the last ten years, Electron’s core has gained plenty of native capabilities that you might need to build your application. Written in C++ and Objective-C, Electron has [dozens of easy-to-use APIs for deeper operating-system integration](https://www.electronjs.org/docs/latest/api/app) — like advanced window customization for transparent or oddly shaped widgets, receiving push notifications from the Apple Push Notification Network, or handling a custom URL protocol for your app.
|
||||
- **Open source**: The entire stack is open source and open to your inspection. This ensures your freedom to add any feature or fix any bug you might encounter in the future.
|
||||
- **Native code when you need it:** It bears repeating that Electron allows you to mix and match web technologies and C++, C, Objective-C, Rust, and other native languages. Whether it be SQLite, a whole LLM, or just the ability to call one specific native API, Electron will make it easy.
|
||||
|
||||
---
|
||||
|
||||
## Why choose something else
|
||||
|
||||
As outlined above, the web is an amazing platform for building interfaces. That doesn’t mean that we, the maintainers, would build _everything_ with HTML and CSS. Here are some notable exceptions:
|
||||
|
||||
**Resource-Constrained Environments and IoT:** In scenarios with very limited memory or processing power (say, one megabyte of memory and 100MHz of processing power on a low-powered ARM Cortex-M), you will likely need to use a low-level language to directly talk to the display to output basic text and images. Even on slightly higher-powered single-chip devices you might want to consider an embedded UI framework. A classic example is a smart watch.
|
||||
|
||||
**Small Disk Footprint**: Zipped Electron apps are usually around 80 to 100 Megabytes. If a smaller disk footprint is a hard requirement, you’ll have to use something else.
|
||||
|
||||
**Operating System UI Frameworks and Libraries**: By allowing you to write native code, Electron can do anything a native application can do, including the use of the operating system’s UI components, like WinUI, SwiftUI, or AppKit. In practice, most Electron apps make rare use of that ability. If you want the majority of your app to be built with operating system-provided interface components, you’ll likely be better off building fully native apps for each operating system you’d like to target. It’s not that it’s impossible with Electron, it’ll just likely be an overall easier development process.
|
||||
|
||||
**Games and Real-Time Graphics:** If you're building a high-performance game or application requiring complex real-time 3D graphics, native frameworks like Unity, Unreal Engine, or DirectX/OpenGL will provide better performance and more direct access to graphics hardware. Web fans might point out caveats, like the fact that even Unreal Engine ships with Chromium — or that WebGPU and WebGL are developing rapidly and many game engines, including the ones listed here, can now output their games in a format that runs in a browser. That said, if you asked us to build the next AAA game, we’d likely use something else than just web technologies.
|
||||
|
||||
**Embedding Lightweight Websites**: Electron apps typically are mostly web apps with native code sprinkled in where useful. Processing-heavy Electron applications tend to write the UI in HTML/CSS and build the backend in Rust, C++, or another native language. If you’re planning to build a primarily native application that also wants to display a little website in a specific view, you might be better off using the OS-provided web view or something like [ultralight](https://ultralig.ht/).
|
||||
@@ -159,12 +159,8 @@ filenames = {
|
||||
"shell/browser/osr/osr_web_contents_view_mac.mm",
|
||||
"shell/browser/relauncher_mac.cc",
|
||||
"shell/browser/ui/certificate_trust_mac.mm",
|
||||
"shell/browser/ui/cocoa/delayed_native_view_host.h",
|
||||
"shell/browser/ui/cocoa/delayed_native_view_host.mm",
|
||||
"shell/browser/ui/cocoa/electron_bundle_mover.h",
|
||||
"shell/browser/ui/cocoa/electron_bundle_mover.mm",
|
||||
"shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h",
|
||||
"shell/browser/ui/cocoa/electron_inspectable_web_contents_view.mm",
|
||||
"shell/browser/ui/cocoa/electron_menu_controller.h",
|
||||
"shell/browser/ui/cocoa/electron_menu_controller.mm",
|
||||
"shell/browser/ui/cocoa/electron_native_widget_mac.h",
|
||||
@@ -191,8 +187,6 @@ filenames = {
|
||||
"shell/browser/ui/cocoa/window_buttons_proxy.mm",
|
||||
"shell/browser/ui/drag_util_mac.mm",
|
||||
"shell/browser/ui/file_dialog_mac.mm",
|
||||
"shell/browser/ui/inspectable_web_contents_view_mac.h",
|
||||
"shell/browser/ui/inspectable_web_contents_view_mac.mm",
|
||||
"shell/browser/ui/message_box_mac.mm",
|
||||
"shell/browser/ui/tray_icon_cocoa.h",
|
||||
"shell/browser/ui/tray_icon_cocoa.mm",
|
||||
@@ -224,8 +218,6 @@ filenames = {
|
||||
"shell/browser/ui/views/electron_views_delegate.h",
|
||||
"shell/browser/ui/views/frameless_view.cc",
|
||||
"shell/browser/ui/views/frameless_view.h",
|
||||
"shell/browser/ui/views/inspectable_web_contents_view_views.cc",
|
||||
"shell/browser/ui/views/inspectable_web_contents_view_views.h",
|
||||
"shell/browser/ui/views/menu_bar.cc",
|
||||
"shell/browser/ui/views/menu_bar.h",
|
||||
"shell/browser/ui/views/menu_delegate.cc",
|
||||
|
||||
@@ -133,6 +133,8 @@ osr_shared_texture_remove_keyed_mutex_on_win_dxgi.patch
|
||||
feat_allow_usage_of_sccontentsharingpicker_on_supported_platforms.patch
|
||||
chore_partial_revert_of.patch
|
||||
fix_software_compositing_infinite_loop.patch
|
||||
fix_add_method_which_disables_headless_mode_on_native_widget.patch
|
||||
fix_put_nsvisualeffectview_before_viewscompositorsuperview.patch
|
||||
refactor_unfilter_unresponsive_events.patch
|
||||
build_disable_thin_lto_mac.patch
|
||||
build_add_public_config_simdutf_config.patch
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Cezary Kulakowski <cezary@openfin.co>
|
||||
Date: Mon, 22 Jul 2024 16:23:13 +0200
|
||||
Subject: fix: add method which disables headless mode on native widget
|
||||
|
||||
We need this method as we create window in headless mode and we
|
||||
switch it back to normal mode only after inital paint is done in
|
||||
order to get some events like WebContents.beginFrameSubscription.
|
||||
If we don't set `is_headless_` to false then some child windows
|
||||
e.g. autofill popups will be created in headless mode leading to
|
||||
ui problems (like dissapearing popup during typing in html's
|
||||
input list.
|
||||
|
||||
diff --git a/ui/views/widget/widget.h b/ui/views/widget/widget.h
|
||||
index 30d0ea6633cb0f7f9aab37951a38be9b0482a12a..734e91e6d50c8d3afd20b39167c6254e934e7c1e 100644
|
||||
--- a/ui/views/widget/widget.h
|
||||
+++ b/ui/views/widget/widget.h
|
||||
@@ -1144,6 +1144,8 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate,
|
||||
// True if widget was created in headless mode.
|
||||
bool is_headless() const { return is_headless_; }
|
||||
|
||||
+ void DisableHeadlessMode() { is_headless_ = false; }
|
||||
+
|
||||
// True if the window size will follow the content preferred size.
|
||||
bool is_autosized() const { return is_autosized_; }
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Micha=C5=82=20Pichli=C5=84ski?=
|
||||
<michal.pichlinski@openfin.co>
|
||||
Date: Tue, 29 Oct 2024 21:16:29 +0100
|
||||
Subject: fix: Put NSVisualEffectView before ViewsCompositorSuperview
|
||||
|
||||
Upstreamed at https://chromium-review.googlesource.com/c/chromium/src/+/6030552
|
||||
|
||||
Otherwise when using `vibrancy` in `BrowserWindow` NSVisualEffectView
|
||||
will hide content displayed by the compositor.
|
||||
|
||||
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
|
||||
index 07c3997e6565cf77362ee73959c4d21da4fefe96..3353a7847df90b58eec34ea4d6ff8fb19617f5cc 100644
|
||||
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
|
||||
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
|
||||
@@ -223,8 +223,19 @@ NSComparisonResult SubviewSorter(__kindof NSView* lhs,
|
||||
void* rank_as_void) {
|
||||
DCHECK_NE(lhs, rhs);
|
||||
|
||||
- if ([lhs isKindOfClass:[ViewsCompositorSuperview class]])
|
||||
+
|
||||
+ // Put NSVisualEffectView before ViewsCompositorSuperview otherwise when using
|
||||
+ // `vibrancy` in `BrowserWindow` NSVisualEffectView will hide content
|
||||
+ // displayed by the compositor.
|
||||
+ if ([lhs isKindOfClass:[NSVisualEffectView class]]) {
|
||||
return NSOrderedAscending;
|
||||
+ }
|
||||
+ if ([lhs isKindOfClass:[ViewsCompositorSuperview class]]) {
|
||||
+ if ([rhs isKindOfClass:[NSVisualEffectView class]]) {
|
||||
+ return NSOrderedDescending;
|
||||
+ }
|
||||
+ return NSOrderedAscending;
|
||||
+ }
|
||||
|
||||
const RankMap* rank = static_cast<const RankMap*>(rank_as_void);
|
||||
auto left_rank = rank->find(lhs);
|
||||
@@ -604,6 +604,19 @@ index 402be34ab888cdf834d0fb65de0832e9a8021ced..82ddc92a35d824926c30279e660cc4e8
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS)
|
||||
diff --git a/chrome/browser/printing/printer_query_oop.cc b/chrome/browser/printing/printer_query_oop.cc
|
||||
index e271d17b3261c47f3dbeaf9be0b3c4229ee27181..00b906660580aca7d5aabcde54d68c2161ea71dd 100644
|
||||
--- a/chrome/browser/printing/printer_query_oop.cc
|
||||
+++ b/chrome/browser/printing/printer_query_oop.cc
|
||||
@@ -127,7 +127,7 @@ void PrinterQueryOop::OnDidAskUserForSettings(
|
||||
std::unique_ptr<PrintSettings> new_settings,
|
||||
mojom::ResultCode result) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
- if (result == mojom::ResultCode::kSuccess) {
|
||||
+ if (result == mojom::ResultCode::kSuccess && query_with_ui_client_id_) {
|
||||
// Want the same PrintBackend service as the query so that we use the same
|
||||
// device context.
|
||||
print_document_client_id_ =
|
||||
diff --git a/components/printing/browser/print_manager.cc b/components/printing/browser/print_manager.cc
|
||||
index 21c81377d32ae8d4185598a7eba88ed1d2063ef0..0767f4e9369e926b1cea99178c1a1975941f1765 100644
|
||||
--- a/components/printing/browser/print_manager.cc
|
||||
@@ -653,7 +666,7 @@ index 6809c4576c71bc1e1a6ad4e0a37707272a9a10f4..3aad10424a6a31dab2ca393d00149ec6
|
||||
PrintingFailed(int32 cookie, PrintFailureReason reason);
|
||||
|
||||
diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc
|
||||
index 18a8d64167b66d0de67c0c89779af90814b827c6..33079deee8720a447e2b4e1f3601542b59e1cf16 100644
|
||||
index 18a8d64167b66d0de67c0c89779af90814b827c6..52b95469f0392fbb108bef3f6d5ea0f8a81410fd 100644
|
||||
--- a/components/printing/renderer/print_render_frame_helper.cc
|
||||
+++ b/components/printing/renderer/print_render_frame_helper.cc
|
||||
@@ -52,6 +52,7 @@
|
||||
@@ -771,7 +784,7 @@ index 18a8d64167b66d0de67c0c89779af90814b827c6..33079deee8720a447e2b4e1f3601542b
|
||||
// Check if `this` is still valid.
|
||||
if (!self)
|
||||
return;
|
||||
@@ -2359,29 +2374,37 @@ void PrintRenderFrameHelper::IPCProcessed() {
|
||||
@@ -2359,29 +2374,43 @@ void PrintRenderFrameHelper::IPCProcessed() {
|
||||
}
|
||||
|
||||
bool PrintRenderFrameHelper::InitPrintSettings(blink::WebLocalFrame* frame,
|
||||
@@ -803,10 +816,18 @@ index 18a8d64167b66d0de67c0c89779af90814b827c6..33079deee8720a447e2b4e1f3601542b
|
||||
|
||||
bool center_on_paper = !IsPrintingPdfFrame(frame, node);
|
||||
- settings.params->print_scaling_option =
|
||||
+ settings->params->print_scaling_option =
|
||||
center_on_paper ? mojom::PrintScalingOption::kCenterShrinkToFitPaper
|
||||
: mojom::PrintScalingOption::kSourceSize;
|
||||
- center_on_paper ? mojom::PrintScalingOption::kCenterShrinkToFitPaper
|
||||
- : mojom::PrintScalingOption::kSourceSize;
|
||||
- RecordDebugEvent(settings.params->printed_doc_type ==
|
||||
+ bool silent = new_settings.FindBool("silent").value_or(false);
|
||||
+ if (silent) {
|
||||
+ settings->params->print_scaling_option = mojom::PrintScalingOption::kFitToPrintableArea;
|
||||
+ } else {
|
||||
+ settings->params->print_scaling_option =
|
||||
+ center_on_paper ? mojom::PrintScalingOption::kCenterShrinkToFitPaper
|
||||
+ : mojom::PrintScalingOption::kSourceSize;
|
||||
+ }
|
||||
+
|
||||
+ RecordDebugEvent(settings->params->printed_doc_type ==
|
||||
mojom::SkiaDocumentType::kMSKP
|
||||
? DebugEvent::kSetPrintSettings5
|
||||
|
||||
@@ -369,9 +369,6 @@ def main():
|
||||
patch_file.close()
|
||||
os.unlink(patch_file.name)
|
||||
else:
|
||||
print(
|
||||
'To patch these files, run:',
|
||||
f"$ git apply {patch_file.name}", sep='\n')
|
||||
filename=patch_file.name
|
||||
print(f"\nTo patch these files, run:\n$ git apply {filename}\n")
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "shell/common/node_includes.h"
|
||||
|
||||
using content::TracingController;
|
||||
using namespace std::literals;
|
||||
|
||||
namespace gin {
|
||||
|
||||
@@ -69,9 +71,9 @@ void StopTracing(gin_helper::Promise<base::FilePath> promise,
|
||||
std::optional<base::FilePath> file_path) {
|
||||
auto resolve_or_reject = base::BindOnce(
|
||||
[](gin_helper::Promise<base::FilePath> promise,
|
||||
const base::FilePath& path, std::optional<std::string> error) {
|
||||
if (error) {
|
||||
promise.RejectWithErrorMessage(error.value());
|
||||
const base::FilePath& path, const std::string_view error) {
|
||||
if (!std::empty(error)) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
} else {
|
||||
promise.Resolve(path);
|
||||
}
|
||||
@@ -81,21 +83,17 @@ void StopTracing(gin_helper::Promise<base::FilePath> promise,
|
||||
auto* instance = TracingController::GetInstance();
|
||||
if (!instance->IsTracing()) {
|
||||
std::move(resolve_or_reject)
|
||||
.Run(std::make_optional(
|
||||
"Failed to stop tracing - no trace in progress"));
|
||||
.Run("Failed to stop tracing - no trace in progress"sv);
|
||||
} else if (file_path) {
|
||||
auto split_callback = base::SplitOnceCallback(std::move(resolve_or_reject));
|
||||
auto endpoint = TracingController::CreateFileEndpoint(
|
||||
*file_path,
|
||||
base::BindOnce(std::move(split_callback.first), std::nullopt));
|
||||
*file_path, base::BindOnce(std::move(split_callback.first), ""sv));
|
||||
if (!instance->StopTracing(endpoint)) {
|
||||
std::move(split_callback.second)
|
||||
.Run(std::make_optional("Failed to stop tracing"));
|
||||
std::move(split_callback.second).Run("Failed to stop tracing"sv);
|
||||
}
|
||||
} else {
|
||||
std::move(resolve_or_reject)
|
||||
.Run(std::make_optional(
|
||||
"Failed to create temporary file for trace data"));
|
||||
.Run("Failed to create temporary file for trace data"sv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,29 +27,14 @@
|
||||
#include "ui/views/view_class_properties.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
#include "shell/browser/ui/cocoa/delayed_native_view_host.h"
|
||||
#endif
|
||||
|
||||
namespace electron::api {
|
||||
|
||||
WebContentsView::WebContentsView(v8::Isolate* isolate,
|
||||
gin::Handle<WebContents> web_contents)
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
: View(new DelayedNativeViewHost(web_contents->inspectable_web_contents()
|
||||
->GetView()
|
||||
->GetNativeView())),
|
||||
#else
|
||||
: View(web_contents->inspectable_web_contents()->GetView()->GetView()),
|
||||
#endif
|
||||
: View(web_contents->inspectable_web_contents()->GetView()),
|
||||
web_contents_(isolate, web_contents.ToV8()),
|
||||
api_web_contents_(web_contents.get()) {
|
||||
#if !BUILDFLAG(IS_MAC)
|
||||
// On macOS the View is a newly-created |DelayedNativeViewHost| and it is our
|
||||
// responsibility to delete it. On other platforms the View is created and
|
||||
// managed by InspectableWebContents.
|
||||
set_delete_view(false);
|
||||
#endif
|
||||
view()->SetProperty(
|
||||
views::kFlexBehaviorKey,
|
||||
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
|
||||
|
||||
@@ -405,9 +405,9 @@ void ElectronBrowserClient::OverrideWebkitPrefs(
|
||||
prefs->javascript_enabled = true;
|
||||
prefs->web_security_enabled = true;
|
||||
prefs->plugins_enabled = true;
|
||||
prefs->dom_paste_enabled = true;
|
||||
prefs->dom_paste_enabled = false;
|
||||
prefs->javascript_can_access_clipboard = false;
|
||||
prefs->allow_scripts_to_close_windows = true;
|
||||
prefs->javascript_can_access_clipboard = true;
|
||||
prefs->local_storage_enabled = true;
|
||||
prefs->databases_enabled = true;
|
||||
prefs->allow_universal_access_from_file_urls =
|
||||
|
||||
@@ -490,14 +490,8 @@ std::unique_ptr<UserScript> ParseUserScript(
|
||||
ConvertRegisteredContentScriptToSerializedUserScript(
|
||||
std::move(content_script));
|
||||
|
||||
std::unique_ptr<UserScript> user_script =
|
||||
script_serialization::ParseSerializedUserScript(
|
||||
serialized_script, extension, allowed_in_incognito, error);
|
||||
if (!user_script) {
|
||||
return nullptr; // Parsing failed.
|
||||
}
|
||||
|
||||
return user_script;
|
||||
return script_serialization::ParseSerializedUserScript(
|
||||
serialized_script, extension, allowed_in_incognito, error);
|
||||
}
|
||||
|
||||
// Converts a UserScript object to a api::scripting::RegisteredContentScript
|
||||
|
||||
@@ -651,7 +651,16 @@ bool TabsUpdateFunction::UpdateURL(const std::string& url_string,
|
||||
// will stay in the omnibox - see https://crbug.com/1085779.
|
||||
load_params.transition_type = ui::PAGE_TRANSITION_FROM_API;
|
||||
|
||||
web_contents_->GetController().LoadURLWithParams(load_params);
|
||||
base::WeakPtr<content::NavigationHandle> navigation_handle =
|
||||
web_contents_->GetController().LoadURLWithParams(load_params);
|
||||
// Navigation can fail for any number of reasons at the content layer.
|
||||
// Unfortunately, we can't provide a detailed error message here, because
|
||||
// there are too many possible triggers. At least notify the extension that
|
||||
// the update failed.
|
||||
if (!navigation_handle) {
|
||||
*error = "Navigation rejected.";
|
||||
return false;
|
||||
}
|
||||
|
||||
DCHECK_EQ(url,
|
||||
web_contents_->GetController().GetPendingEntry()->GetVirtualURL());
|
||||
|
||||
@@ -461,7 +461,7 @@ FileSystemAccessPermissionContext::GetReadPermissionGrant(
|
||||
// but that is exactly what we want.
|
||||
auto& origin_state = active_permissions_map_[origin];
|
||||
auto*& existing_grant = origin_state.read_grants[path_info.path];
|
||||
scoped_refptr<PermissionGrantImpl> new_grant;
|
||||
scoped_refptr<PermissionGrantImpl> grant;
|
||||
|
||||
if (existing_grant && existing_grant->handle_type() != handle_type) {
|
||||
// |path| changed from being a directory to being a file or vice versa,
|
||||
@@ -471,18 +471,21 @@ FileSystemAccessPermissionContext::GetReadPermissionGrant(
|
||||
existing_grant = nullptr;
|
||||
}
|
||||
|
||||
if (!existing_grant) {
|
||||
new_grant = base::MakeRefCounted<PermissionGrantImpl>(
|
||||
bool creating_new_grant = !existing_grant;
|
||||
if (creating_new_grant) {
|
||||
grant = base::MakeRefCounted<PermissionGrantImpl>(
|
||||
weak_factory_.GetWeakPtr(), origin, path_info, handle_type,
|
||||
GrantType::kRead, user_action);
|
||||
existing_grant = new_grant.get();
|
||||
existing_grant = grant.get();
|
||||
} else {
|
||||
grant = existing_grant;
|
||||
}
|
||||
|
||||
// If a parent directory is already readable this new grant should also be
|
||||
// readable.
|
||||
if (new_grant &&
|
||||
if (creating_new_grant &&
|
||||
AncestorHasActivePermission(origin, path_info.path, GrantType::kRead)) {
|
||||
existing_grant->SetStatus(PermissionStatus::GRANTED);
|
||||
grant->SetStatus(PermissionStatus::GRANTED);
|
||||
} else {
|
||||
switch (user_action) {
|
||||
case UserAction::kOpen:
|
||||
@@ -494,7 +497,7 @@ FileSystemAccessPermissionContext::GetReadPermissionGrant(
|
||||
[[fallthrough]];
|
||||
case UserAction::kDragAndDrop:
|
||||
// Drag&drop grants read access for all handles.
|
||||
existing_grant->SetStatus(PermissionStatus::GRANTED);
|
||||
grant->SetStatus(PermissionStatus::GRANTED);
|
||||
break;
|
||||
case UserAction::kLoadFromStorage:
|
||||
case UserAction::kNone:
|
||||
@@ -502,7 +505,7 @@ FileSystemAccessPermissionContext::GetReadPermissionGrant(
|
||||
}
|
||||
}
|
||||
|
||||
return existing_grant;
|
||||
return grant;
|
||||
}
|
||||
|
||||
scoped_refptr<content::FileSystemAccessPermissionGrant>
|
||||
@@ -516,7 +519,7 @@ FileSystemAccessPermissionContext::GetWritePermissionGrant(
|
||||
// but that is exactly what we want.
|
||||
auto& origin_state = active_permissions_map_[origin];
|
||||
auto*& existing_grant = origin_state.write_grants[path_info.path];
|
||||
scoped_refptr<PermissionGrantImpl> new_grant;
|
||||
scoped_refptr<PermissionGrantImpl> grant;
|
||||
|
||||
if (existing_grant && existing_grant->handle_type() != handle_type) {
|
||||
// |path| changed from being a directory to being a file or vice versa,
|
||||
@@ -526,23 +529,26 @@ FileSystemAccessPermissionContext::GetWritePermissionGrant(
|
||||
existing_grant = nullptr;
|
||||
}
|
||||
|
||||
if (!existing_grant) {
|
||||
new_grant = base::MakeRefCounted<PermissionGrantImpl>(
|
||||
bool creating_new_grant = !existing_grant;
|
||||
if (creating_new_grant) {
|
||||
grant = base::MakeRefCounted<PermissionGrantImpl>(
|
||||
weak_factory_.GetWeakPtr(), origin, path_info, handle_type,
|
||||
GrantType::kWrite, user_action);
|
||||
existing_grant = new_grant.get();
|
||||
existing_grant = grant.get();
|
||||
} else {
|
||||
grant = existing_grant;
|
||||
}
|
||||
|
||||
// If a parent directory is already writable this new grant should also be
|
||||
// writable.
|
||||
if (new_grant &&
|
||||
if (creating_new_grant &&
|
||||
AncestorHasActivePermission(origin, path_info.path, GrantType::kWrite)) {
|
||||
existing_grant->SetStatus(PermissionStatus::GRANTED);
|
||||
grant->SetStatus(PermissionStatus::GRANTED);
|
||||
} else {
|
||||
switch (user_action) {
|
||||
case UserAction::kSave:
|
||||
// Only automatically grant write access for save dialogs.
|
||||
existing_grant->SetStatus(PermissionStatus::GRANTED);
|
||||
grant->SetStatus(PermissionStatus::GRANTED);
|
||||
break;
|
||||
case UserAction::kOpen:
|
||||
case UserAction::kDragAndDrop:
|
||||
@@ -552,7 +558,7 @@ FileSystemAccessPermissionContext::GetWritePermissionGrant(
|
||||
}
|
||||
}
|
||||
|
||||
return existing_grant;
|
||||
return grant;
|
||||
}
|
||||
|
||||
bool FileSystemAccessPermissionContext::IsFileTypeDangerous(
|
||||
@@ -588,7 +594,7 @@ void FileSystemAccessPermissionContext::ConfirmSensitiveEntryAccess(
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
base::OnceCallback<void(SensitiveEntryResult)> callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
callback_ = std::move(callback);
|
||||
callback_map_.try_emplace(path_info.path, std::move(callback));
|
||||
|
||||
auto after_blocklist_check_callback = base::BindOnce(
|
||||
&FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist,
|
||||
@@ -632,16 +638,18 @@ void FileSystemAccessPermissionContext::PerformAfterWriteChecks(
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::RunRestrictedPathCallback(
|
||||
const base::FilePath& file_path,
|
||||
SensitiveEntryResult result) {
|
||||
if (callback_)
|
||||
std::move(callback_).Run(result);
|
||||
if (auto val = callback_map_.extract(file_path))
|
||||
std::move(val.mapped()).Run(result);
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::OnRestrictedPathResult(
|
||||
const base::FilePath& file_path,
|
||||
gin::Arguments* args) {
|
||||
SensitiveEntryResult result = SensitiveEntryResult::kAbort;
|
||||
args->GetNext(&result);
|
||||
RunRestrictedPathCallback(result);
|
||||
RunRestrictedPathCallback(file_path, result);
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
|
||||
@@ -654,8 +662,9 @@ void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (user_action == UserAction::kNone) {
|
||||
RunRestrictedPathCallback(should_block ? SensitiveEntryResult::kAbort
|
||||
: SensitiveEntryResult::kAllowed);
|
||||
auto result = should_block ? SensitiveEntryResult::kAbort
|
||||
: SensitiveEntryResult::kAllowed;
|
||||
RunRestrictedPathCallback(path_info.path, result);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -674,11 +683,11 @@ void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
|
||||
"file-system-access-restricted", details,
|
||||
base::BindRepeating(
|
||||
&FileSystemAccessPermissionContext::OnRestrictedPathResult,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
weak_factory_.GetWeakPtr(), path_info.path));
|
||||
return;
|
||||
}
|
||||
|
||||
RunRestrictedPathCallback(SensitiveEntryResult::kAllowed);
|
||||
RunRestrictedPathCallback(path_info.path, SensitiveEntryResult::kAllowed);
|
||||
}
|
||||
|
||||
void FileSystemAccessPermissionContext::MaybeEvictEntries(
|
||||
@@ -866,12 +875,22 @@ void FileSystemAccessPermissionContext::RevokeActiveGrants(
|
||||
auto origin_it = active_permissions_map_.find(origin);
|
||||
if (origin_it != active_permissions_map_.end()) {
|
||||
OriginState& origin_state = origin_it->second;
|
||||
for (auto& grant : origin_state.read_grants) {
|
||||
for (auto grant_iter = origin_state.read_grants.begin(),
|
||||
grant_end = origin_state.read_grants.end();
|
||||
grant_iter != grant_end;) {
|
||||
// The grant may be removed from `read_grants`, so increase the iterator
|
||||
// before continuing.
|
||||
auto& grant = *(grant_iter++);
|
||||
if (file_path.empty() || grant.first == file_path) {
|
||||
grant.second->SetStatus(PermissionStatus::ASK);
|
||||
}
|
||||
}
|
||||
for (auto& grant : origin_state.write_grants) {
|
||||
for (auto grant_iter = origin_state.write_grants.begin(),
|
||||
grant_end = origin_state.write_grants.end();
|
||||
grant_iter != grant_end;) {
|
||||
// The grant may be removed from `write_grants`, so increase the iterator
|
||||
// before continuing.
|
||||
auto& grant = *(grant_iter++);
|
||||
if (file_path.empty() || grant.first == file_path) {
|
||||
grant.second->SetStatus(PermissionStatus::ASK);
|
||||
}
|
||||
|
||||
@@ -144,9 +144,11 @@ class FileSystemAccessPermissionContext
|
||||
content::GlobalRenderFrameHostId frame_id,
|
||||
bool should_block);
|
||||
|
||||
void RunRestrictedPathCallback(SensitiveEntryResult result);
|
||||
void RunRestrictedPathCallback(const base::FilePath& file_path,
|
||||
SensitiveEntryResult result);
|
||||
|
||||
void OnRestrictedPathResult(gin::Arguments* args);
|
||||
void OnRestrictedPathResult(const base::FilePath& file_path,
|
||||
gin::Arguments* args);
|
||||
|
||||
void MaybeEvictEntries(base::Value::Dict& dict);
|
||||
|
||||
@@ -170,7 +172,8 @@ class FileSystemAccessPermissionContext
|
||||
|
||||
std::map<url::Origin, base::Value::Dict> id_pathinfo_map_;
|
||||
|
||||
base::OnceCallback<void(SensitiveEntryResult)> callback_;
|
||||
std::map<base::FilePath, base::OnceCallback<void(SensitiveEntryResult)>>
|
||||
callback_map_;
|
||||
|
||||
base::WeakPtrFactory<FileSystemAccessPermissionContext> weak_factory_{this};
|
||||
};
|
||||
|
||||
@@ -169,9 +169,6 @@ class NativeWindowMac : public NativeWindow,
|
||||
void NotifyWindowDidFailToEnterFullScreen();
|
||||
void NotifyWindowWillLeaveFullScreen();
|
||||
|
||||
// views::WidgetDelegate:
|
||||
views::View* GetContentsView() override;
|
||||
|
||||
// Cleanup observers when window is getting closed. Note that the destructor
|
||||
// can be called much later after window gets closed, so we should not do
|
||||
// cleanup in destructor.
|
||||
@@ -223,6 +220,7 @@ class NativeWindowMac : public NativeWindow,
|
||||
|
||||
protected:
|
||||
// views::WidgetDelegate:
|
||||
views::View* GetContentsView() override;
|
||||
bool CanMaximize() const override;
|
||||
std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameView(
|
||||
views::Widget* widget) override;
|
||||
|
||||
@@ -194,6 +194,8 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
|
||||
params.bounds = bounds;
|
||||
params.delegate = this;
|
||||
params.type = views::Widget::InitParams::TYPE_WINDOW;
|
||||
// Allow painting before shown, to be later disabled in ElectronNSWindow.
|
||||
params.headless_mode = true;
|
||||
params.native_widget =
|
||||
new ElectronNativeWidgetMac(this, windowType, styleMask, widget());
|
||||
widget()->Init(std::move(params));
|
||||
@@ -1674,6 +1676,7 @@ void NativeWindowMac::Cleanup() {
|
||||
DCHECK(!IsClosed());
|
||||
ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this);
|
||||
display::Screen::GetScreen()->RemoveObserver(this);
|
||||
[window_ cleanup];
|
||||
}
|
||||
|
||||
class NativeAppWindowFrameViewMac : public views::NativeFrameViewMac {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "content/public/browser/desktop_media_id.h"
|
||||
#include "content/public/common/color_parser.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view.h"
|
||||
#include "shell/browser/ui/views/root_view.h"
|
||||
#include "shell/browser/web_contents_preferences.h"
|
||||
#include "shell/browser/web_view_manager.h"
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
|
||||
#define ELECTRON_SHELL_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
|
||||
|
||||
#include "ui/views/controls/native/native_view_host.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
// Automatically attach the native view after the NativeViewHost is attached to
|
||||
// a widget. (Attaching it directly would cause crash.)
|
||||
class DelayedNativeViewHost : public views::NativeViewHost {
|
||||
public:
|
||||
explicit DelayedNativeViewHost(gfx::NativeView native_view);
|
||||
~DelayedNativeViewHost() override;
|
||||
|
||||
// disable copy
|
||||
DelayedNativeViewHost(const DelayedNativeViewHost&) = delete;
|
||||
DelayedNativeViewHost& operator=(const DelayedNativeViewHost&) = delete;
|
||||
|
||||
// views::View:
|
||||
void ViewHierarchyChanged(
|
||||
const views::ViewHierarchyChangedDetails& details) override;
|
||||
|
||||
private:
|
||||
gfx::NativeView native_view_;
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/ui/cocoa/delayed_native_view_host.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
DelayedNativeViewHost::DelayedNativeViewHost(gfx::NativeView native_view)
|
||||
: native_view_(native_view) {}
|
||||
|
||||
DelayedNativeViewHost::~DelayedNativeViewHost() = default;
|
||||
|
||||
void DelayedNativeViewHost::ViewHierarchyChanged(
|
||||
const views::ViewHierarchyChangedDetails& details) {
|
||||
if (!details.is_add && native_view())
|
||||
Detach();
|
||||
NativeViewHost::ViewHierarchyChanged(details);
|
||||
if (details.is_add && GetWidget() && !native_view())
|
||||
Attach(native_view_);
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
#define ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
#include "base/apple/owned_objc.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
|
||||
#include "ui/base/cocoa/base_view.h"
|
||||
|
||||
namespace electron {
|
||||
class InspectableWebContentsViewMac;
|
||||
}
|
||||
|
||||
using electron::InspectableWebContentsViewMac;
|
||||
|
||||
@interface NSView (WebContentsView)
|
||||
- (void)setMouseDownCanMoveWindow:(BOOL)can_move;
|
||||
@end
|
||||
|
||||
@interface ElectronInspectableWebContentsView : BaseView <NSWindowDelegate> {
|
||||
@private
|
||||
raw_ptr<electron::InspectableWebContentsViewMac> inspectableWebContentsView_;
|
||||
|
||||
NSView* __strong fake_view_;
|
||||
NSWindow* __strong devtools_window_;
|
||||
BOOL devtools_visible_;
|
||||
BOOL devtools_docked_;
|
||||
BOOL devtools_is_first_responder_;
|
||||
BOOL attached_to_window_;
|
||||
|
||||
DevToolsContentsResizingStrategy strategy_;
|
||||
}
|
||||
|
||||
- (instancetype)initWithInspectableWebContentsViewMac:
|
||||
(InspectableWebContentsViewMac*)view;
|
||||
- (void)notifyDevToolsFocused;
|
||||
- (void)setCornerRadii:(CGFloat)cornerRadius;
|
||||
- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate;
|
||||
- (BOOL)isDevToolsVisible;
|
||||
- (BOOL)isDevToolsFocused;
|
||||
- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate;
|
||||
- (void)setContentsResizingStrategy:
|
||||
(const DevToolsContentsResizingStrategy&)strategy;
|
||||
- (void)setTitle:(NSString*)title;
|
||||
- (NSString*)getTitle;
|
||||
|
||||
@end
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
@@ -1,337 +0,0 @@
|
||||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#include "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h"
|
||||
|
||||
#include "content/public/browser/render_widget_host_view.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/ui/cocoa/event_dispatching_window.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view_mac.h"
|
||||
#include "ui/base/cocoa/base_view.h"
|
||||
#include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h"
|
||||
|
||||
@implementation ElectronInspectableWebContentsView
|
||||
|
||||
- (instancetype)initWithInspectableWebContentsViewMac:
|
||||
(InspectableWebContentsViewMac*)view {
|
||||
self = [super init];
|
||||
if (!self)
|
||||
return nil;
|
||||
|
||||
inspectableWebContentsView_ = view;
|
||||
devtools_visible_ = NO;
|
||||
devtools_docked_ = NO;
|
||||
devtools_is_first_responder_ = NO;
|
||||
attached_to_window_ = NO;
|
||||
|
||||
if (inspectableWebContentsView_->inspectable_web_contents()->is_guest()) {
|
||||
fake_view_ = [[NSView alloc] init];
|
||||
[fake_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
||||
[self addSubview:fake_view_];
|
||||
} else {
|
||||
auto* contents = inspectableWebContentsView_->inspectable_web_contents()
|
||||
->GetWebContents();
|
||||
auto* contentsView = contents->GetNativeView().GetNativeNSView();
|
||||
[contentsView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
||||
[self addSubview:contentsView];
|
||||
}
|
||||
|
||||
// See https://code.google.com/p/chromium/issues/detail?id=348490.
|
||||
[self setWantsLayer:YES];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
|
||||
[self adjustSubviews];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow {
|
||||
if (attached_to_window_ && !self.window) {
|
||||
attached_to_window_ = NO;
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
} else if (!attached_to_window_ && self.window) {
|
||||
attached_to_window_ = YES;
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(viewDidBecomeFirstResponder:)
|
||||
name:kViewDidBecomeFirstResponder
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(parentWindowBecameMain:)
|
||||
name:NSWindowDidBecomeMainNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showDevTools:(id)sender {
|
||||
inspectableWebContentsView_->inspectable_web_contents()->ShowDevTools(true);
|
||||
}
|
||||
|
||||
- (void)notifyDevToolsFocused {
|
||||
if (inspectableWebContentsView_->GetDelegate())
|
||||
inspectableWebContentsView_->GetDelegate()->DevToolsFocused();
|
||||
}
|
||||
|
||||
- (void)setCornerRadii:(CGFloat)cornerRadius {
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
DCHECK(inspectable_web_contents);
|
||||
auto* webContents = inspectable_web_contents->GetWebContents();
|
||||
if (!webContents)
|
||||
return;
|
||||
auto* webContentsView = webContents->GetNativeView().GetNativeNSView();
|
||||
webContentsView.wantsLayer = YES;
|
||||
webContentsView.layer.cornerRadius = cornerRadius;
|
||||
}
|
||||
|
||||
- (void)notifyDevToolsResized {
|
||||
// When devtools is opened, resizing devtools would not trigger
|
||||
// UpdateDraggableRegions for WebContents, so we have to notify the window
|
||||
// to do an update of draggable regions.
|
||||
if (inspectableWebContentsView_->GetDelegate())
|
||||
inspectableWebContentsView_->GetDelegate()->DevToolsResized();
|
||||
}
|
||||
|
||||
- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate {
|
||||
if (visible == devtools_visible_)
|
||||
return;
|
||||
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
auto* devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
devtools_visible_ = visible;
|
||||
if (devtools_docked_) {
|
||||
if (visible) {
|
||||
// Place the devToolsView under contentsView, notice that we didn't set
|
||||
// sizes for them until the setContentsResizingStrategy message.
|
||||
[self addSubview:devToolsView positioned:NSWindowBelow relativeTo:nil];
|
||||
[self adjustSubviews];
|
||||
|
||||
// Focus on web view.
|
||||
devToolsWebContents->RestoreFocus();
|
||||
} else {
|
||||
gfx::ScopedCocoaDisableScreenUpdates disabler;
|
||||
[devToolsView removeFromSuperview];
|
||||
[self adjustSubviews];
|
||||
[self notifyDevToolsResized];
|
||||
}
|
||||
} else {
|
||||
if (visible) {
|
||||
if (activate) {
|
||||
[devtools_window_ makeKeyAndOrderFront:nil];
|
||||
} else {
|
||||
[devtools_window_ orderBack:nil];
|
||||
}
|
||||
} else {
|
||||
[devtools_window_ setDelegate:nil];
|
||||
[devtools_window_ close];
|
||||
devtools_window_ = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isDevToolsVisible {
|
||||
return devtools_visible_;
|
||||
}
|
||||
|
||||
- (BOOL)isDevToolsFocused {
|
||||
if (devtools_docked_) {
|
||||
return [[self window] isKeyWindow] && devtools_is_first_responder_;
|
||||
} else {
|
||||
return [devtools_window_ isKeyWindow];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove NSWindowStyleMaskTexturedBackground.
|
||||
// https://github.com/electron/electron/issues/43125
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate {
|
||||
// Revert to no-devtools state.
|
||||
[self setDevToolsVisible:NO activate:NO];
|
||||
|
||||
// Switch to new state.
|
||||
devtools_docked_ = docked;
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
if (!docked) {
|
||||
auto styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSWindowStyleMaskMiniaturizable |
|
||||
NSWindowStyleMaskResizable |
|
||||
NSWindowStyleMaskTexturedBackground |
|
||||
NSWindowStyleMaskUnifiedTitleAndToolbar;
|
||||
devtools_window_ = [[EventDispatchingWindow alloc]
|
||||
initWithContentRect:NSMakeRect(0, 0, 800, 600)
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:YES];
|
||||
[devtools_window_ setDelegate:self];
|
||||
[devtools_window_ setFrameAutosaveName:@"electron.devtools"];
|
||||
[devtools_window_ setTitle:@"Developer Tools"];
|
||||
[devtools_window_ setReleasedWhenClosed:NO];
|
||||
[devtools_window_ setAutorecalculatesContentBorderThickness:NO
|
||||
forEdge:NSMaxYEdge];
|
||||
[devtools_window_ setContentBorderThickness:24 forEdge:NSMaxYEdge];
|
||||
|
||||
NSView* contentView = [devtools_window_ contentView];
|
||||
devToolsView.frame = contentView.bounds;
|
||||
devToolsView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
|
||||
[contentView addSubview:devToolsView];
|
||||
[devToolsView setMouseDownCanMoveWindow:NO];
|
||||
} else {
|
||||
[devToolsView setMouseDownCanMoveWindow:YES];
|
||||
}
|
||||
[self setDevToolsVisible:YES activate:activate];
|
||||
}
|
||||
|
||||
// -Wdeprecated-declarations
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (void)setContentsResizingStrategy:
|
||||
(const DevToolsContentsResizingStrategy&)strategy {
|
||||
strategy_.CopyFrom(strategy);
|
||||
[self adjustSubviews];
|
||||
}
|
||||
|
||||
- (void)adjustSubviews {
|
||||
if (![[self subviews] count])
|
||||
return;
|
||||
|
||||
if (![self isDevToolsVisible] || devtools_window_) {
|
||||
DCHECK_EQ(1u, [[self subviews] count]);
|
||||
NSView* contents = [[self subviews] objectAtIndex:0];
|
||||
[contents setFrame:[self bounds]];
|
||||
return;
|
||||
}
|
||||
|
||||
NSView* devToolsView = [[self subviews] objectAtIndex:0];
|
||||
NSView* contentsView = [[self subviews] objectAtIndex:1];
|
||||
|
||||
DCHECK_EQ(2u, [[self subviews] count]);
|
||||
|
||||
gfx::Rect new_devtools_bounds;
|
||||
gfx::Rect new_contents_bounds;
|
||||
ApplyDevToolsContentsResizingStrategy(
|
||||
strategy_, gfx::Size(NSSizeToCGSize([self bounds].size)),
|
||||
&new_devtools_bounds, &new_contents_bounds);
|
||||
[devToolsView setFrame:[self flipRectToNSRect:new_devtools_bounds]];
|
||||
[contentsView setFrame:[self flipRectToNSRect:new_contents_bounds]];
|
||||
|
||||
// Move mask to the devtools area to exclude it from dragging.
|
||||
NSRect cf = contentsView.frame;
|
||||
NSRect sb = [self bounds];
|
||||
NSRect devtools_frame;
|
||||
if (cf.size.height < sb.size.height) { // bottom docked
|
||||
devtools_frame.origin.x = 0;
|
||||
devtools_frame.origin.y = 0;
|
||||
devtools_frame.size.width = sb.size.width;
|
||||
devtools_frame.size.height = sb.size.height - cf.size.height;
|
||||
} else { // left or right docked
|
||||
if (cf.origin.x > 0) // left docked
|
||||
devtools_frame.origin.x = 0;
|
||||
else // right docked.
|
||||
devtools_frame.origin.x = cf.size.width;
|
||||
devtools_frame.origin.y = 0;
|
||||
devtools_frame.size.width = sb.size.width - cf.size.width;
|
||||
devtools_frame.size.height = sb.size.height;
|
||||
}
|
||||
|
||||
[self notifyDevToolsResized];
|
||||
}
|
||||
|
||||
- (void)setTitle:(NSString*)title {
|
||||
[devtools_window_ setTitle:title];
|
||||
}
|
||||
|
||||
- (NSString*)getTitle {
|
||||
return [devtools_window_ title];
|
||||
}
|
||||
|
||||
- (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
DCHECK(inspectable_web_contents);
|
||||
auto* webContents = inspectable_web_contents->GetWebContents();
|
||||
if (!webContents)
|
||||
return;
|
||||
auto* webContentsView = webContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
NSView* view = [notification object];
|
||||
if ([[webContentsView subviews] containsObject:view]) {
|
||||
devtools_is_first_responder_ = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
if (!devToolsWebContents)
|
||||
return;
|
||||
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
if ([[devToolsView subviews] containsObject:view]) {
|
||||
devtools_is_first_responder_ = YES;
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parentWindowBecameMain:(NSNotification*)notification {
|
||||
NSWindow* parentWindow = [notification object];
|
||||
if ([self window] == parentWindow && devtools_docked_ &&
|
||||
devtools_is_first_responder_)
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
|
||||
#pragma mark - NSWindowDelegate
|
||||
|
||||
- (void)windowWillClose:(NSNotification*)notification {
|
||||
inspectableWebContentsView_->inspectable_web_contents()->CloseDevTools();
|
||||
}
|
||||
|
||||
- (void)windowDidBecomeMain:(NSNotification*)notification {
|
||||
content::WebContents* web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents()
|
||||
->GetDevToolsWebContents();
|
||||
if (!web_contents)
|
||||
return;
|
||||
|
||||
web_contents->RestoreFocus();
|
||||
|
||||
content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
|
||||
if (rwhv)
|
||||
rwhv->SetActive(true);
|
||||
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
|
||||
- (void)windowDidResignMain:(NSNotification*)notification {
|
||||
content::WebContents* web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents()
|
||||
->GetDevToolsWebContents();
|
||||
if (!web_contents)
|
||||
return;
|
||||
|
||||
web_contents->StoreFocus();
|
||||
|
||||
content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
|
||||
if (rwhv)
|
||||
rwhv->SetActive(false);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -29,6 +29,8 @@ class ScopedDisableResize {
|
||||
|
||||
} // namespace electron
|
||||
|
||||
class ElectronNativeWindowObserver;
|
||||
|
||||
@interface ElectronNSWindow : NativeWidgetMacNSWindow {
|
||||
@private
|
||||
raw_ptr<electron::NativeWindowMac> shell_;
|
||||
@@ -41,6 +43,7 @@ class ScopedDisableResize {
|
||||
@property(nonatomic, retain) NSImage* cornerMask;
|
||||
- (id)initWithShell:(electron::NativeWindowMac*)shell
|
||||
styleMask:(NSUInteger)styleMask;
|
||||
- (void)cleanup;
|
||||
- (electron::NativeWindowMac*)shell;
|
||||
- (id)accessibilityFocusedUIElement;
|
||||
- (NSRect)originalContentRectForFrameRect:(NSRect)frameRect;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include "electron/mas.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/native_window_mac.h"
|
||||
#include "shell/browser/ui/cocoa/delayed_native_view_host.h"
|
||||
#include "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h"
|
||||
#include "shell/browser/ui/cocoa/electron_preview_item.h"
|
||||
#include "shell/browser/ui/cocoa/electron_touch_bar.h"
|
||||
#include "shell/browser/ui/cocoa/root_view_mac.h"
|
||||
@@ -113,6 +111,7 @@ void SwizzleSwipeWithEvent(NSView* view, SEL swiz_selector) {
|
||||
method_getImplementation(new_swipe_with_event));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
@implementation ElectronNSWindow
|
||||
@@ -168,6 +167,10 @@ void SwizzleSwipeWithEvent(NSView* view, SEL swiz_selector) {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)cleanup {
|
||||
shell_ = nullptr;
|
||||
}
|
||||
|
||||
- (electron::NativeWindowMac*)shell {
|
||||
return shell_;
|
||||
}
|
||||
@@ -255,6 +258,17 @@ void SwizzleSwipeWithEvent(NSView* view, SEL swiz_selector) {
|
||||
[super setFrame:windowFrame display:displayViews];
|
||||
}
|
||||
|
||||
- (void)orderWindow:(NSWindowOrderingMode)place relativeTo:(NSInteger)otherWin {
|
||||
if (shell_) {
|
||||
// We initialize the window in headless mode to allow painting before it is
|
||||
// shown, but we don't want the headless behavior of allowing the window to
|
||||
// be placed unconstrained.
|
||||
self.isHeadless = false;
|
||||
shell_->widget()->DisableHeadlessMode();
|
||||
}
|
||||
[super orderWindow:place relativeTo:otherWin];
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute {
|
||||
if ([attribute isEqual:NSAccessibilityEnabledAttribute])
|
||||
return [NSNumber numberWithBool:YES];
|
||||
|
||||
@@ -365,6 +365,19 @@ using FullScreenTransitionState =
|
||||
shell_->GetNativeWindow());
|
||||
auto* bridged_view = bridge_host->GetInProcessNSWindowBridge();
|
||||
bridged_view->OnWindowWillClose();
|
||||
|
||||
// Native widget and its compositor have been destroyed upon close. We need
|
||||
// to detach contents view in order to prevent reusing its layer without
|
||||
// compositor in the `WebContentsViewMac::CreateViewForWidget`, leading to
|
||||
// `DCHECK` failure in `BrowserCompositorMac::SetParentUiLayer`.
|
||||
auto* contents_view =
|
||||
static_cast<views::WidgetDelegate*>(shell_)->GetContentsView();
|
||||
if (contents_view) {
|
||||
auto* parent = contents_view->parent();
|
||||
if (parent) {
|
||||
parent->RemoveChildView(contents_view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)windowShouldClose:(id)window {
|
||||
|
||||
@@ -297,11 +297,6 @@ class InspectableWebContents::NetworkResourceLoader
|
||||
base::TimeDelta retry_delay_;
|
||||
};
|
||||
|
||||
// Implemented separately on each platform.
|
||||
InspectableWebContentsView* CreateInspectableContentsView(
|
||||
InspectableWebContents* inspectable_web_contents);
|
||||
|
||||
// static
|
||||
// static
|
||||
void InspectableWebContents::RegisterPrefs(PrefRegistrySimple* registry) {
|
||||
registry->RegisterDictionaryPref(kDevToolsBoundsPref,
|
||||
@@ -317,7 +312,7 @@ InspectableWebContents::InspectableWebContents(
|
||||
: pref_service_(pref_service),
|
||||
web_contents_(std::move(web_contents)),
|
||||
is_guest_(is_guest),
|
||||
view_(CreateInspectableContentsView(this)) {
|
||||
view_(new InspectableWebContentsView(this)) {
|
||||
const base::Value* bounds_dict =
|
||||
&pref_service_->GetValue(kDevToolsBoundsPref);
|
||||
if (bounds_dict->is_dict()) {
|
||||
|
||||
@@ -5,12 +5,250 @@
|
||||
|
||||
#include "shell/browser/ui/inspectable_web_contents_view.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "shell/browser/ui/drag_util.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_delegate.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "ui/base/models/image_model.h"
|
||||
#include "ui/views/controls/label.h"
|
||||
#include "ui/views/controls/webview/webview.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/views/widget/widget_delegate.h"
|
||||
#include "ui/views/window/client_view.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
class DevToolsWindowDelegate : public views::ClientView,
|
||||
public views::WidgetDelegate {
|
||||
public:
|
||||
DevToolsWindowDelegate(InspectableWebContentsView* shell,
|
||||
views::View* view,
|
||||
views::Widget* widget)
|
||||
: views::ClientView(widget, view),
|
||||
shell_(shell),
|
||||
view_(view),
|
||||
widget_(widget) {
|
||||
SetOwnedByWidget(true);
|
||||
set_owned_by_client();
|
||||
|
||||
if (shell->GetDelegate())
|
||||
icon_ = shell->GetDelegate()->GetDevToolsWindowIcon();
|
||||
}
|
||||
~DevToolsWindowDelegate() override = default;
|
||||
|
||||
// disable copy
|
||||
DevToolsWindowDelegate(const DevToolsWindowDelegate&) = delete;
|
||||
DevToolsWindowDelegate& operator=(const DevToolsWindowDelegate&) = delete;
|
||||
|
||||
// views::WidgetDelegate:
|
||||
views::View* GetInitiallyFocusedView() override { return view_; }
|
||||
std::u16string GetWindowTitle() const override { return shell_->GetTitle(); }
|
||||
ui::ImageModel GetWindowAppIcon() override { return GetWindowIcon(); }
|
||||
ui::ImageModel GetWindowIcon() override { return icon_; }
|
||||
views::Widget* GetWidget() override { return widget_; }
|
||||
const views::Widget* GetWidget() const override { return widget_; }
|
||||
views::View* GetContentsView() override { return view_; }
|
||||
views::ClientView* CreateClientView(views::Widget* widget) override {
|
||||
return this;
|
||||
}
|
||||
|
||||
// views::ClientView:
|
||||
views::CloseRequestResult OnWindowCloseRequested() override {
|
||||
shell_->inspectable_web_contents()->CloseDevTools();
|
||||
return views::CloseRequestResult::kCannotClose;
|
||||
}
|
||||
|
||||
private:
|
||||
raw_ptr<InspectableWebContentsView> shell_;
|
||||
raw_ptr<views::View> view_;
|
||||
raw_ptr<views::Widget> widget_;
|
||||
ui::ImageModel icon_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
InspectableWebContentsView::InspectableWebContentsView(
|
||||
InspectableWebContents* inspectable_web_contents)
|
||||
: inspectable_web_contents_(inspectable_web_contents) {}
|
||||
: inspectable_web_contents_(inspectable_web_contents),
|
||||
devtools_web_view_(new views::WebView(nullptr)),
|
||||
title_(u"Developer Tools") {
|
||||
if (!inspectable_web_contents_->is_guest() &&
|
||||
inspectable_web_contents_->GetWebContents()->GetNativeView()) {
|
||||
auto* contents_web_view = new views::WebView(nullptr);
|
||||
contents_web_view->SetWebContents(
|
||||
inspectable_web_contents_->GetWebContents());
|
||||
contents_web_view_ = contents_web_view;
|
||||
} else {
|
||||
no_contents_view_ = new views::Label(u"No content under offscreen mode");
|
||||
}
|
||||
|
||||
InspectableWebContentsView::~InspectableWebContentsView() = default;
|
||||
devtools_web_view_->SetVisible(false);
|
||||
AddChildView(devtools_web_view_.get());
|
||||
AddChildView(GetContentsView());
|
||||
}
|
||||
|
||||
InspectableWebContentsView::~InspectableWebContentsView() {
|
||||
if (devtools_window_)
|
||||
inspectable_web_contents()->SaveDevToolsBounds(
|
||||
devtools_window_->GetWindowBoundsInScreen());
|
||||
}
|
||||
|
||||
void InspectableWebContentsView::SetCornerRadii(
|
||||
const gfx::RoundedCornersF& corner_radii) {
|
||||
// WebView won't exist for offscreen rendering.
|
||||
if (contents_web_view_) {
|
||||
contents_web_view_->holder()->SetCornerRadii(corner_radii);
|
||||
}
|
||||
}
|
||||
|
||||
void InspectableWebContentsView::ShowDevTools(bool activate) {
|
||||
if (devtools_visible_)
|
||||
return;
|
||||
|
||||
devtools_visible_ = true;
|
||||
if (devtools_window_) {
|
||||
devtools_window_web_view_->SetWebContents(
|
||||
inspectable_web_contents_->GetDevToolsWebContents());
|
||||
devtools_window_->SetBounds(inspectable_web_contents()->dev_tools_bounds());
|
||||
if (activate) {
|
||||
devtools_window_->Show();
|
||||
} else {
|
||||
devtools_window_->ShowInactive();
|
||||
}
|
||||
|
||||
// Update draggable regions to account for the new dock position.
|
||||
if (GetDelegate())
|
||||
GetDelegate()->DevToolsResized();
|
||||
} else {
|
||||
devtools_web_view_->SetVisible(true);
|
||||
devtools_web_view_->SetWebContents(
|
||||
inspectable_web_contents_->GetDevToolsWebContents());
|
||||
devtools_web_view_->RequestFocus();
|
||||
DeprecatedLayoutImmediately();
|
||||
}
|
||||
}
|
||||
|
||||
void InspectableWebContentsView::CloseDevTools() {
|
||||
if (!devtools_visible_)
|
||||
return;
|
||||
|
||||
devtools_visible_ = false;
|
||||
if (devtools_window_) {
|
||||
auto save_bounds = devtools_window_->IsMinimized()
|
||||
? devtools_window_->GetRestoredBounds()
|
||||
: devtools_window_->GetWindowBoundsInScreen();
|
||||
inspectable_web_contents()->SaveDevToolsBounds(save_bounds);
|
||||
|
||||
devtools_window_.reset();
|
||||
devtools_window_web_view_ = nullptr;
|
||||
devtools_window_delegate_ = nullptr;
|
||||
} else {
|
||||
devtools_web_view_->SetVisible(false);
|
||||
devtools_web_view_->SetWebContents(nullptr);
|
||||
DeprecatedLayoutImmediately();
|
||||
}
|
||||
}
|
||||
|
||||
bool InspectableWebContentsView::IsDevToolsViewShowing() {
|
||||
return devtools_visible_;
|
||||
}
|
||||
|
||||
bool InspectableWebContentsView::IsDevToolsViewFocused() {
|
||||
if (devtools_window_web_view_)
|
||||
return devtools_window_web_view_->HasFocus();
|
||||
else if (devtools_web_view_)
|
||||
return devtools_web_view_->HasFocus();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void InspectableWebContentsView::SetIsDocked(bool docked, bool activate) {
|
||||
CloseDevTools();
|
||||
|
||||
if (!docked) {
|
||||
devtools_window_ = std::make_unique<views::Widget>();
|
||||
devtools_window_web_view_ = new views::WebView(nullptr);
|
||||
devtools_window_delegate_ = new DevToolsWindowDelegate(
|
||||
this, devtools_window_web_view_, devtools_window_.get());
|
||||
|
||||
views::Widget::InitParams params(
|
||||
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
|
||||
views::Widget::InitParams::TYPE_WINDOW);
|
||||
params.delegate = devtools_window_delegate_;
|
||||
params.bounds = inspectable_web_contents()->dev_tools_bounds();
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
params.wm_role_name = "devtools";
|
||||
if (GetDelegate())
|
||||
GetDelegate()->GetDevToolsWindowWMClass(¶ms.wm_class_name,
|
||||
¶ms.wm_class_class);
|
||||
#endif
|
||||
|
||||
devtools_window_->Init(std::move(params));
|
||||
devtools_window_->UpdateWindowIcon();
|
||||
devtools_window_->widget_delegate()->SetHasWindowSizeControls(true);
|
||||
}
|
||||
|
||||
ShowDevTools(activate);
|
||||
}
|
||||
|
||||
void InspectableWebContentsView::SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) {
|
||||
strategy_.CopyFrom(strategy);
|
||||
DeprecatedLayoutImmediately();
|
||||
}
|
||||
|
||||
void InspectableWebContentsView::SetTitle(const std::u16string& title) {
|
||||
if (devtools_window_) {
|
||||
title_ = title;
|
||||
devtools_window_->UpdateWindowTitle();
|
||||
}
|
||||
}
|
||||
|
||||
const std::u16string InspectableWebContentsView::GetTitle() {
|
||||
return title_;
|
||||
}
|
||||
|
||||
void InspectableWebContentsView::Layout(PassKey) {
|
||||
if (!devtools_web_view_->GetVisible()) {
|
||||
GetContentsView()->SetBoundsRect(GetContentsBounds());
|
||||
// Propagate layout call to all children, for example browser views.
|
||||
LayoutSuperclass<View>(this);
|
||||
return;
|
||||
}
|
||||
|
||||
gfx::Size container_size(width(), height());
|
||||
gfx::Rect new_devtools_bounds;
|
||||
gfx::Rect new_contents_bounds;
|
||||
ApplyDevToolsContentsResizingStrategy(
|
||||
strategy_, container_size, &new_devtools_bounds, &new_contents_bounds);
|
||||
|
||||
// DevTools cares about the specific position, so we have to compensate RTL
|
||||
// layout here.
|
||||
new_devtools_bounds.set_x(GetMirroredXForRect(new_devtools_bounds));
|
||||
new_contents_bounds.set_x(GetMirroredXForRect(new_contents_bounds));
|
||||
|
||||
devtools_web_view_->SetBoundsRect(new_devtools_bounds);
|
||||
GetContentsView()->SetBoundsRect(new_contents_bounds);
|
||||
|
||||
// Propagate layout call to all children, for example browser views.
|
||||
LayoutSuperclass<View>(this);
|
||||
|
||||
if (GetDelegate())
|
||||
GetDelegate()->DevToolsResized();
|
||||
}
|
||||
|
||||
views::View* InspectableWebContentsView::GetContentsView() const {
|
||||
DCHECK(contents_web_view_ || no_contents_view_);
|
||||
|
||||
return contents_web_view_ ? contents_web_view_ : no_contents_view_;
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -9,13 +9,9 @@
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(TOOLKIT_VIEWS) && !BUILDFLAG(IS_MAC)
|
||||
#include "ui/views/view.h"
|
||||
#else
|
||||
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
|
||||
#include "ui/gfx/native_widget_types.h"
|
||||
#endif
|
||||
#include "ui/views/view.h"
|
||||
|
||||
class DevToolsContentsResizingStrategy;
|
||||
|
||||
@@ -23,23 +19,22 @@ namespace gfx {
|
||||
class RoundedCornersF;
|
||||
} // namespace gfx
|
||||
|
||||
#if defined(TOOLKIT_VIEWS)
|
||||
namespace views {
|
||||
class View;
|
||||
class WebView;
|
||||
class Widget;
|
||||
class WidgetDelegate;
|
||||
} // namespace views
|
||||
#endif
|
||||
|
||||
namespace electron {
|
||||
|
||||
class InspectableWebContents;
|
||||
class InspectableWebContentsViewDelegate;
|
||||
|
||||
class InspectableWebContentsView {
|
||||
class InspectableWebContentsView : public views::View {
|
||||
public:
|
||||
explicit InspectableWebContentsView(
|
||||
InspectableWebContents* inspectable_web_contents);
|
||||
virtual ~InspectableWebContentsView();
|
||||
~InspectableWebContentsView() override;
|
||||
|
||||
InspectableWebContents* inspectable_web_contents() {
|
||||
return inspectable_web_contents_;
|
||||
@@ -51,32 +46,40 @@ class InspectableWebContentsView {
|
||||
}
|
||||
InspectableWebContentsViewDelegate* GetDelegate() const { return delegate_; }
|
||||
|
||||
#if defined(TOOLKIT_VIEWS) && !BUILDFLAG(IS_MAC)
|
||||
// Returns the container control, which has devtools view attached.
|
||||
virtual views::View* GetView() = 0;
|
||||
#else
|
||||
virtual gfx::NativeView GetNativeView() const = 0;
|
||||
#endif
|
||||
void SetCornerRadii(const gfx::RoundedCornersF& corner_radii);
|
||||
|
||||
virtual void ShowDevTools(bool activate) = 0;
|
||||
virtual void SetCornerRadii(const gfx::RoundedCornersF& corner_radii) = 0;
|
||||
// Hide the DevTools view.
|
||||
virtual void CloseDevTools() = 0;
|
||||
virtual bool IsDevToolsViewShowing() = 0;
|
||||
virtual bool IsDevToolsViewFocused() = 0;
|
||||
virtual void SetIsDocked(bool docked, bool activate) = 0;
|
||||
virtual void SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) = 0;
|
||||
virtual void SetTitle(const std::u16string& title) = 0;
|
||||
virtual const std::u16string GetTitle() = 0;
|
||||
void ShowDevTools(bool activate);
|
||||
void CloseDevTools();
|
||||
bool IsDevToolsViewShowing();
|
||||
bool IsDevToolsViewFocused();
|
||||
void SetIsDocked(bool docked, bool activate);
|
||||
void SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy);
|
||||
void SetTitle(const std::u16string& title);
|
||||
const std::u16string GetTitle();
|
||||
|
||||
// views::View:
|
||||
void Layout(PassKey) override;
|
||||
|
||||
private:
|
||||
views::View* GetContentsView() const;
|
||||
|
||||
protected:
|
||||
// Owns us.
|
||||
raw_ptr<InspectableWebContents> inspectable_web_contents_;
|
||||
|
||||
private:
|
||||
raw_ptr<InspectableWebContentsViewDelegate> delegate_ =
|
||||
nullptr; // weak references.
|
||||
|
||||
std::unique_ptr<views::Widget> devtools_window_;
|
||||
raw_ptr<views::WebView> devtools_window_web_view_ = nullptr;
|
||||
raw_ptr<views::WebView> contents_web_view_ = nullptr;
|
||||
raw_ptr<views::View> no_contents_view_ = nullptr;
|
||||
raw_ptr<views::WebView> devtools_web_view_ = nullptr;
|
||||
|
||||
DevToolsContentsResizingStrategy strategy_;
|
||||
bool devtools_visible_ = false;
|
||||
raw_ptr<views::WidgetDelegate> devtools_window_delegate_ = nullptr;
|
||||
std::u16string title_;
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_
|
||||
#define ELECTRON_SHELL_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_
|
||||
|
||||
#include "shell/browser/ui/inspectable_web_contents_view.h"
|
||||
|
||||
@class ElectronInspectableWebContentsView;
|
||||
|
||||
namespace electron {
|
||||
|
||||
class InspectableWebContentsViewMac : public InspectableWebContentsView {
|
||||
public:
|
||||
explicit InspectableWebContentsViewMac(
|
||||
InspectableWebContents* inspectable_web_contents);
|
||||
InspectableWebContentsViewMac(const InspectableWebContentsViewMac&) = delete;
|
||||
InspectableWebContentsViewMac& operator=(
|
||||
const InspectableWebContentsViewMac&) = delete;
|
||||
~InspectableWebContentsViewMac() override;
|
||||
|
||||
gfx::NativeView GetNativeView() const override;
|
||||
void SetCornerRadii(const gfx::RoundedCornersF& corner_radii) override;
|
||||
void ShowDevTools(bool activate) override;
|
||||
void CloseDevTools() override;
|
||||
bool IsDevToolsViewShowing() override;
|
||||
bool IsDevToolsViewFocused() override;
|
||||
void SetIsDocked(bool docked, bool activate) override;
|
||||
void SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) override;
|
||||
void SetTitle(const std::u16string& title) override;
|
||||
const std::u16string GetTitle() override;
|
||||
|
||||
private:
|
||||
ElectronInspectableWebContentsView* __strong view_;
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#include "shell/browser/ui/inspectable_web_contents_view_mac.h"
|
||||
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#import "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "ui/gfx/geometry/rounded_corners_f.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
InspectableWebContentsView* CreateInspectableContentsView(
|
||||
InspectableWebContents* inspectable_web_contents) {
|
||||
return new InspectableWebContentsViewMac(inspectable_web_contents);
|
||||
}
|
||||
|
||||
InspectableWebContentsViewMac::InspectableWebContentsViewMac(
|
||||
InspectableWebContents* inspectable_web_contents)
|
||||
: InspectableWebContentsView(inspectable_web_contents),
|
||||
view_([[ElectronInspectableWebContentsView alloc]
|
||||
initWithInspectableWebContentsViewMac:this]) {}
|
||||
|
||||
InspectableWebContentsViewMac::~InspectableWebContentsViewMac() {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:view_];
|
||||
CloseDevTools();
|
||||
}
|
||||
|
||||
gfx::NativeView InspectableWebContentsViewMac::GetNativeView() const {
|
||||
return view_;
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::SetCornerRadii(
|
||||
const gfx::RoundedCornersF& corner_radii) {
|
||||
// We can assume all four values are identical.
|
||||
[view_ setCornerRadii:corner_radii.upper_left()];
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::ShowDevTools(bool activate) {
|
||||
[view_ setDevToolsVisible:YES activate:activate];
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::CloseDevTools() {
|
||||
[view_ setDevToolsVisible:NO activate:NO];
|
||||
}
|
||||
|
||||
bool InspectableWebContentsViewMac::IsDevToolsViewShowing() {
|
||||
return [view_ isDevToolsVisible];
|
||||
}
|
||||
|
||||
bool InspectableWebContentsViewMac::IsDevToolsViewFocused() {
|
||||
return [view_ isDevToolsFocused];
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::SetIsDocked(bool docked, bool activate) {
|
||||
[view_ setIsDocked:docked activate:activate];
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) {
|
||||
[view_ setContentsResizingStrategy:strategy];
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::SetTitle(const std::u16string& title) {
|
||||
[view_ setTitle:base::SysUTF16ToNSString(title)];
|
||||
}
|
||||
|
||||
const std::u16string InspectableWebContentsViewMac::GetTitle() {
|
||||
return base::SysNSStringToUTF16([view_ getTitle]);
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "shell/browser/ui/views/frameless_view.h"
|
||||
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/base/hit_test.h"
|
||||
#include "ui/base/metadata/metadata_impl_macros.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#include "shell/browser/ui/views/inspectable_web_contents_view_views.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "shell/browser/ui/drag_util.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_delegate.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "ui/base/models/image_model.h"
|
||||
#include "ui/gfx/geometry/rounded_corners_f.h"
|
||||
#include "ui/views/controls/label.h"
|
||||
#include "ui/views/controls/webview/webview.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/views/widget/widget_delegate.h"
|
||||
#include "ui/views/window/client_view.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
class DevToolsWindowDelegate : public views::ClientView,
|
||||
public views::WidgetDelegate {
|
||||
public:
|
||||
DevToolsWindowDelegate(InspectableWebContentsViewViews* shell,
|
||||
views::View* view,
|
||||
views::Widget* widget)
|
||||
: views::ClientView(widget, view),
|
||||
shell_(shell),
|
||||
view_(view),
|
||||
widget_(widget) {
|
||||
SetOwnedByWidget(true);
|
||||
set_owned_by_client();
|
||||
|
||||
if (shell->GetDelegate())
|
||||
icon_ = shell->GetDelegate()->GetDevToolsWindowIcon();
|
||||
}
|
||||
~DevToolsWindowDelegate() override = default;
|
||||
|
||||
// disable copy
|
||||
DevToolsWindowDelegate(const DevToolsWindowDelegate&) = delete;
|
||||
DevToolsWindowDelegate& operator=(const DevToolsWindowDelegate&) = delete;
|
||||
|
||||
// views::WidgetDelegate:
|
||||
views::View* GetInitiallyFocusedView() override { return view_; }
|
||||
std::u16string GetWindowTitle() const override { return shell_->GetTitle(); }
|
||||
ui::ImageModel GetWindowAppIcon() override { return GetWindowIcon(); }
|
||||
ui::ImageModel GetWindowIcon() override { return icon_; }
|
||||
views::Widget* GetWidget() override { return widget_; }
|
||||
const views::Widget* GetWidget() const override { return widget_; }
|
||||
views::View* GetContentsView() override { return view_; }
|
||||
views::ClientView* CreateClientView(views::Widget* widget) override {
|
||||
return this;
|
||||
}
|
||||
|
||||
// views::ClientView:
|
||||
views::CloseRequestResult OnWindowCloseRequested() override {
|
||||
shell_->inspectable_web_contents()->CloseDevTools();
|
||||
return views::CloseRequestResult::kCannotClose;
|
||||
}
|
||||
|
||||
private:
|
||||
raw_ptr<InspectableWebContentsViewViews> shell_;
|
||||
raw_ptr<views::View> view_;
|
||||
raw_ptr<views::Widget> widget_;
|
||||
ui::ImageModel icon_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
InspectableWebContentsView* CreateInspectableContentsView(
|
||||
InspectableWebContents* inspectable_web_contents) {
|
||||
return new InspectableWebContentsViewViews(inspectable_web_contents);
|
||||
}
|
||||
|
||||
InspectableWebContentsViewViews::InspectableWebContentsViewViews(
|
||||
InspectableWebContents* inspectable_web_contents)
|
||||
: InspectableWebContentsView(inspectable_web_contents),
|
||||
devtools_web_view_(new views::WebView(nullptr)),
|
||||
title_(u"Developer Tools") {
|
||||
if (!inspectable_web_contents_->is_guest() &&
|
||||
inspectable_web_contents_->GetWebContents()->GetNativeView()) {
|
||||
auto* contents_web_view = new views::WebView(nullptr);
|
||||
contents_web_view->SetWebContents(
|
||||
inspectable_web_contents_->GetWebContents());
|
||||
contents_view_ = contents_web_view_ = contents_web_view;
|
||||
} else {
|
||||
contents_view_ = new views::Label(u"No content under offscreen mode");
|
||||
}
|
||||
|
||||
devtools_web_view_->SetVisible(false);
|
||||
AddChildView(devtools_web_view_.get());
|
||||
AddChildView(contents_view_.get());
|
||||
}
|
||||
|
||||
InspectableWebContentsViewViews::~InspectableWebContentsViewViews() {
|
||||
if (devtools_window_)
|
||||
inspectable_web_contents()->SaveDevToolsBounds(
|
||||
devtools_window_->GetWindowBoundsInScreen());
|
||||
}
|
||||
|
||||
views::View* InspectableWebContentsViewViews::GetView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::SetCornerRadii(
|
||||
const gfx::RoundedCornersF& corner_radii) {
|
||||
// WebView won't exist for offscreen rendering.
|
||||
if (contents_web_view_) {
|
||||
contents_web_view_->holder()->SetCornerRadii(
|
||||
gfx::RoundedCornersF(corner_radii));
|
||||
}
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::ShowDevTools(bool activate) {
|
||||
if (devtools_visible_)
|
||||
return;
|
||||
|
||||
devtools_visible_ = true;
|
||||
if (devtools_window_) {
|
||||
devtools_window_web_view_->SetWebContents(
|
||||
inspectable_web_contents_->GetDevToolsWebContents());
|
||||
devtools_window_->SetBounds(inspectable_web_contents()->dev_tools_bounds());
|
||||
if (activate) {
|
||||
devtools_window_->Show();
|
||||
} else {
|
||||
devtools_window_->ShowInactive();
|
||||
}
|
||||
|
||||
// Update draggable regions to account for the new dock position.
|
||||
if (GetDelegate())
|
||||
GetDelegate()->DevToolsResized();
|
||||
} else {
|
||||
devtools_web_view_->SetVisible(true);
|
||||
devtools_web_view_->SetWebContents(
|
||||
inspectable_web_contents_->GetDevToolsWebContents());
|
||||
devtools_web_view_->RequestFocus();
|
||||
DeprecatedLayoutImmediately();
|
||||
}
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::CloseDevTools() {
|
||||
if (!devtools_visible_)
|
||||
return;
|
||||
|
||||
devtools_visible_ = false;
|
||||
if (devtools_window_) {
|
||||
auto save_bounds = devtools_window_->IsMinimized()
|
||||
? devtools_window_->GetRestoredBounds()
|
||||
: devtools_window_->GetWindowBoundsInScreen();
|
||||
inspectable_web_contents()->SaveDevToolsBounds(save_bounds);
|
||||
|
||||
devtools_window_.reset();
|
||||
devtools_window_web_view_ = nullptr;
|
||||
devtools_window_delegate_ = nullptr;
|
||||
} else {
|
||||
devtools_web_view_->SetVisible(false);
|
||||
devtools_web_view_->SetWebContents(nullptr);
|
||||
DeprecatedLayoutImmediately();
|
||||
}
|
||||
}
|
||||
|
||||
bool InspectableWebContentsViewViews::IsDevToolsViewShowing() {
|
||||
return devtools_visible_;
|
||||
}
|
||||
|
||||
bool InspectableWebContentsViewViews::IsDevToolsViewFocused() {
|
||||
if (devtools_window_web_view_)
|
||||
return devtools_window_web_view_->HasFocus();
|
||||
else if (devtools_web_view_)
|
||||
return devtools_web_view_->HasFocus();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::SetIsDocked(bool docked, bool activate) {
|
||||
CloseDevTools();
|
||||
|
||||
if (!docked) {
|
||||
devtools_window_ = std::make_unique<views::Widget>();
|
||||
devtools_window_web_view_ = new views::WebView(nullptr);
|
||||
devtools_window_delegate_ = new DevToolsWindowDelegate(
|
||||
this, devtools_window_web_view_, devtools_window_.get());
|
||||
|
||||
views::Widget::InitParams params{
|
||||
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET};
|
||||
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
|
||||
params.delegate = devtools_window_delegate_;
|
||||
params.bounds = inspectable_web_contents()->dev_tools_bounds();
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
params.wm_role_name = "devtools";
|
||||
if (GetDelegate())
|
||||
GetDelegate()->GetDevToolsWindowWMClass(¶ms.wm_class_name,
|
||||
¶ms.wm_class_class);
|
||||
#endif
|
||||
|
||||
devtools_window_->Init(std::move(params));
|
||||
devtools_window_->UpdateWindowIcon();
|
||||
devtools_window_->widget_delegate()->SetHasWindowSizeControls(true);
|
||||
}
|
||||
|
||||
ShowDevTools(activate);
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) {
|
||||
strategy_.CopyFrom(strategy);
|
||||
DeprecatedLayoutImmediately();
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::SetTitle(const std::u16string& title) {
|
||||
if (devtools_window_) {
|
||||
title_ = title;
|
||||
devtools_window_->UpdateWindowTitle();
|
||||
}
|
||||
}
|
||||
|
||||
const std::u16string InspectableWebContentsViewViews::GetTitle() {
|
||||
return title_;
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::Layout(PassKey) {
|
||||
if (!devtools_web_view_->GetVisible()) {
|
||||
contents_view_->SetBoundsRect(GetContentsBounds());
|
||||
// Propagate layout call to all children, for example browser views.
|
||||
LayoutSuperclass<View>(this);
|
||||
return;
|
||||
}
|
||||
|
||||
gfx::Size container_size(width(), height());
|
||||
gfx::Rect new_devtools_bounds;
|
||||
gfx::Rect new_contents_bounds;
|
||||
ApplyDevToolsContentsResizingStrategy(
|
||||
strategy_, container_size, &new_devtools_bounds, &new_contents_bounds);
|
||||
|
||||
// DevTools cares about the specific position, so we have to compensate RTL
|
||||
// layout here.
|
||||
new_devtools_bounds.set_x(GetMirroredXForRect(new_devtools_bounds));
|
||||
new_contents_bounds.set_x(GetMirroredXForRect(new_contents_bounds));
|
||||
|
||||
devtools_web_view_->SetBoundsRect(new_devtools_bounds);
|
||||
contents_view_->SetBoundsRect(new_contents_bounds);
|
||||
|
||||
// Propagate layout call to all children, for example browser views.
|
||||
LayoutSuperclass<View>(this);
|
||||
|
||||
if (GetDelegate())
|
||||
GetDelegate()->DevToolsResized();
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_
|
||||
#define ELECTRON_SHELL_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view.h"
|
||||
#include "third_party/skia/include/core/SkRegion.h"
|
||||
#include "ui/views/view.h"
|
||||
|
||||
namespace views {
|
||||
class WebView;
|
||||
class Widget;
|
||||
class WidgetDelegate;
|
||||
} // namespace views
|
||||
|
||||
namespace electron {
|
||||
|
||||
class InspectableWebContentsViewViews : public InspectableWebContentsView,
|
||||
public views::View {
|
||||
public:
|
||||
explicit InspectableWebContentsViewViews(
|
||||
InspectableWebContents* inspectable_web_contents);
|
||||
~InspectableWebContentsViewViews() override;
|
||||
|
||||
// InspectableWebContentsView:
|
||||
views::View* GetView() override;
|
||||
void ShowDevTools(bool activate) override;
|
||||
void SetCornerRadii(const gfx::RoundedCornersF& corner_radii) override;
|
||||
void CloseDevTools() override;
|
||||
bool IsDevToolsViewShowing() override;
|
||||
bool IsDevToolsViewFocused() override;
|
||||
void SetIsDocked(bool docked, bool activate) override;
|
||||
void SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) override;
|
||||
void SetTitle(const std::u16string& title) override;
|
||||
const std::u16string GetTitle() override;
|
||||
|
||||
// views::View:
|
||||
void Layout(PassKey) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<views::Widget> devtools_window_;
|
||||
raw_ptr<views::WebView> devtools_window_web_view_ = nullptr;
|
||||
raw_ptr<views::WebView> contents_web_view_ = nullptr;
|
||||
raw_ptr<views::View> contents_view_ = nullptr;
|
||||
raw_ptr<views::WebView> devtools_web_view_ = nullptr;
|
||||
|
||||
DevToolsContentsResizingStrategy strategy_;
|
||||
bool devtools_visible_ = false;
|
||||
raw_ptr<views::WidgetDelegate> devtools_window_delegate_ = nullptr;
|
||||
std::u16string title_;
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_
|
||||
@@ -31,6 +31,11 @@
|
||||
#include "components/services/print_compositor/public/mojom/print_compositor.mojom.h" // nogncheck
|
||||
#endif // BUILDFLAG(ENABLE_PRINTING)
|
||||
|
||||
#if BUILDFLAG(ENABLE_OOP_PRINTING)
|
||||
#include "chrome/services/printing/print_backend_service_impl.h"
|
||||
#include "chrome/services/printing/public/mojom/print_backend_service.mojom.h"
|
||||
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINTING) && BUILDFLAG(IS_WIN)
|
||||
#include "chrome/services/printing/pdf_to_emf_converter_factory.h"
|
||||
#endif
|
||||
@@ -72,6 +77,21 @@ auto RunPrintCompositor(
|
||||
}
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_OOP_PRINTING)
|
||||
auto RunPrintingSandboxedPrintBackendHost(
|
||||
mojo::PendingReceiver<printing::mojom::SandboxedPrintBackendHost>
|
||||
receiver) {
|
||||
return std::make_unique<printing::SandboxedPrintBackendHostImpl>(
|
||||
std::move(receiver));
|
||||
}
|
||||
auto RunPrintingUnsandboxedPrintBackendHost(
|
||||
mojo::PendingReceiver<printing::mojom::UnsandboxedPrintBackendHost>
|
||||
receiver) {
|
||||
return std::make_unique<printing::UnsandboxedPrintBackendHostImpl>(
|
||||
std::move(receiver));
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
|
||||
|
||||
auto RunProxyResolver(
|
||||
mojo::PendingReceiver<proxy_resolver::mojom::ProxyResolverFactory>
|
||||
receiver) {
|
||||
@@ -122,6 +142,11 @@ void ElectronContentUtilityClient::RegisterMainThreadServices(
|
||||
services.Add(RunPrintCompositor);
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_OOP_PRINTING)
|
||||
services.Add(RunPrintingSandboxedPrintBackendHost);
|
||||
services.Add(RunPrintingUnsandboxedPrintBackendHost);
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINT_PREVIEW) || \
|
||||
(BUILDFLAG(ENABLE_PRINTING) && BUILDFLAG(IS_WIN))
|
||||
services.Add(RunPrintingService);
|
||||
|
||||
Reference in New Issue
Block a user