feat: enhance native window.open to match the custom implementation's behavior (#19703)

Co-authored-by: Andy Locascio <andy@slack-corp.com>
This commit is contained in:
Heilig Benedek 2020-03-26 19:05:45 +01:00 committed by GitHub
parent b1f4ac00f0
commit 74372d65ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 350 additions and 143 deletions

View file

@ -0,0 +1,23 @@
# PostBody Object
* `data` Array<[PostData](./post-data.md)> - The post data to be sent to the
new window.
* `contentType` String - The `content-type` header used for the data. One of
`application/x-www-form-urlencoded` or `multipart/form-data`. Corresponds to
the `enctype` attribute of the submitted HTML form.
* `boundary` String (optional) - The boundary used to separate multiple parts of
the message. Only valid when `contentType` is `multipart/form-data`.
Note that keys starting with `--` are not currently supported. For example, this will errantly submit as `multipart/form-data` when `nativeWindowOpen` is set to `false` in webPreferences:
```html
<form
target="_blank"
method="POST"
enctype="application/x-www-form-urlencoded"
action="https://postman-echo.com/post"
>
<input type="text" name="--theKey">
<input type="submit">
</form>
```

View file

@ -0,0 +1,21 @@
# PostData Object
* `type` String - One of the following:
* `rawData` - The data is available as a `Buffer`, in the `rawData` field.
* `file` - The object represents a file. The `filePath`, `offset`, `length`
and `modificationTime` fields will be used to describe the file.
* `blob` - The object represents a `Blob`. The `blobUUID` field will be used
to describe the `Blob`.
* `bytes` String (optional) - The raw bytes of the post data in a `Buffer`.
Required for the `rawData` type.
* `filePath` String (optional) - The path of the file being uploaded. Required
for the `file` type.
* `blobUUID` String (optional) - The `UUID` of the `Blob` being uploaded.
Required for the `blob` type.
* `offset` Integer (optional) - The offset from the beginning of the file being
uploaded, in bytes. Only valid for `file` types.
* `length` Integer (optional) - The length of the file being uploaded, in bytes.
If set to `-1`, the whole file will be uploaded. Only valid for `file` types.
* `modificationTime` Double (optional) - The modification time of the file
represented by a double, which is the number of seconds since the `UNIX Epoch`
(Jan 1, 1970). Only valid for `file` types.

View file

@ -150,6 +150,10 @@ Returns:
* `referrer` [Referrer](structures/referrer.md) - The referrer that will be * `referrer` [Referrer](structures/referrer.md) - The referrer that will be
passed to the new window. May or may not result in the `Referer` header being passed to the new window. May or may not result in the `Referer` header being
sent, depending on the referrer policy. sent, depending on the referrer policy.
* `postBody` [PostBody](structures/post-body.md) (optional) - The post data that
will be sent to the new window, along with the appropriate headers that will
be set. If no post data is to be sent, the value will be `null`. Only defined
when the window is being created by a form that set `target=_blank`.
Emitted when the page requests to open a new window for a `url`. It could be Emitted when the page requests to open a new window for a `url`. It could be
requested by `window.open` or an external link like `<a target='_blank'>`. requested by `window.open` or an external link like `<a target='_blank'>`.
@ -162,7 +166,7 @@ new [`BrowserWindow`](browser-window.md). If you call `event.preventDefault()` a
instance, failing to do so may result in unexpected behavior. For example: instance, failing to do so may result in unexpected behavior. For example:
```javascript ```javascript
myBrowserWindow.webContents.on('new-window', (event, url, frameName, disposition, options) => { myBrowserWindow.webContents.on('new-window', (event, url, frameName, disposition, options, additionalFeatures, referrer, postBody) => {
event.preventDefault() event.preventDefault()
const win = new BrowserWindow({ const win = new BrowserWindow({
webContents: options.webContents, // use existing webContents if provided webContents: options.webContents, // use existing webContents if provided
@ -170,7 +174,16 @@ myBrowserWindow.webContents.on('new-window', (event, url, frameName, disposition
}) })
win.once('ready-to-show', () => win.show()) win.once('ready-to-show', () => win.show())
if (!options.webContents) { if (!options.webContents) {
win.loadURL(url) // existing webContents will be navigated automatically const loadOptions = {
httpReferrer: referrer
}
if (postBody != null) {
const { data, contentType, boundary } = postBody
loadOptions.postData = postBody.data
loadOptions.extraHeaders = `content-type: ${contentType}; boundary=${boundary}`
}
win.loadURL(url, loadOptions) // existing webContents will be navigated automatically
} }
event.newGuest = win event.newGuest = win
}) })

View file

@ -102,6 +102,8 @@ auto_filenames = {
"docs/api/structures/mouse-wheel-input-event.md", "docs/api/structures/mouse-wheel-input-event.md",
"docs/api/structures/notification-action.md", "docs/api/structures/notification-action.md",
"docs/api/structures/point.md", "docs/api/structures/point.md",
"docs/api/structures/post-body.md",
"docs/api/structures/post-data.md",
"docs/api/structures/printer-info.md", "docs/api/structures/printer-info.md",
"docs/api/structures/process-memory-info.md", "docs/api/structures/process-memory-info.md",
"docs/api/structures/process-metric.md", "docs/api/structures/process-metric.md",

View file

@ -126,7 +126,6 @@ class SlurpStream extends Writable {
this._data = Buffer.concat([this._data, chunk]); this._data = Buffer.concat([this._data, chunk]);
callback(); callback();
} }
data () { return this._data; } data () { return this._data; }
} }

View file

@ -11,6 +11,7 @@ const { internalWindowOpen } = require('@electron/internal/browser/guest-window-
const NavigationController = require('@electron/internal/browser/navigation-controller'); const NavigationController = require('@electron/internal/browser/navigation-controller');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const { convertFeaturesString } = require('@electron/internal/common/parse-features-string');
const { MessagePortMain } = require('@electron/internal/browser/message-port-main'); const { MessagePortMain } = require('@electron/internal/browser/message-port-main');
// session is not used here, the purpose is to make sure session is initalized // session is not used here, the purpose is to make sure session is initalized
@ -506,40 +507,42 @@ WebContents.prototype._init = function () {
this.reload(); this.reload();
}); });
// Handle window.open for BrowserWindow and BrowserView. if (this.getType() !== 'remote') {
if (['browserView', 'window'].includes(this.getType())) {
// Make new windows requested by links behave like "window.open". // Make new windows requested by links behave like "window.open".
this.on('-new-window', (event, url, frameName, disposition, this.on('-new-window', (event, url, frameName, disposition,
additionalFeatures, postData, rawFeatures, referrer, postData) => {
referrer) => { const { options, additionalFeatures } = convertFeaturesString(rawFeatures, frameName);
const options = { const mergedOptions = {
show: true, show: true,
width: 800, width: 800,
height: 600 height: 600,
...options
}; };
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, postData);
internalWindowOpen(event, url, referrer, frameName, disposition, mergedOptions, additionalFeatures, postData);
}); });
// Create a new browser window for the native implementation of // Create a new browser window for the native implementation of
// "window.open", used in sandbox and nativeWindowOpen mode. // "window.open", used in sandbox and nativeWindowOpen mode.
this.on('-add-new-contents', (event, webContents, disposition, this.on('-add-new-contents', (event, webContents, disposition,
userGesture, left, top, width, height, url, frameName) => { userGesture, left, top, width, height, url, frameName,
referrer, rawFeatures, postData) => {
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' && if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
disposition !== 'background-tab')) { disposition !== 'background-tab')) {
event.preventDefault(); event.preventDefault();
return; return;
} }
const options = { const { options, additionalFeatures } = convertFeaturesString(rawFeatures, frameName);
const mergedOptions = {
show: true, show: true,
x: left, width: 800,
y: top, height: 600,
width: width || 800, webContents,
height: height || 600, ...options
webContents
}; };
const referrer = { url: '', policy: 'default' };
internalWindowOpen(event, url, referrer, frameName, disposition, options); internalWindowOpen(event, url, referrer, frameName, disposition, mergedOptions, additionalFeatures, postData);
}); });
} }

View file

@ -3,7 +3,7 @@
const { webContents } = require('electron'); const { webContents } = require('electron');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const parseFeaturesString = require('@electron/internal/common/parse-features-string'); const { parseFeaturesString } = require('@electron/internal/common/parse-features-string');
const { syncMethods, asyncMethods, properties } = require('@electron/internal/common/web-view-methods'); const { syncMethods, asyncMethods, properties } = require('@electron/internal/common/web-view-methods');
const { serialize } = require('@electron/internal/common/type-utils'); const { serialize } = require('@electron/internal/common/type-utils');
@ -141,17 +141,6 @@ const createGuest = function (embedder, params) {
} }
}); });
// Forward internal web contents event to embedder to handle
// native window.open setup
guest.on('-add-new-contents', (...args) => {
if (guest.getLastWebPreferences().nativeWindowOpen === true) {
const embedder = getEmbedder(guestInstanceId);
if (embedder != null) {
embedder.emit('-add-new-contents', ...args);
}
}
});
return guestInstanceId; return guestInstanceId;
}; };

View file

@ -5,7 +5,7 @@ const { BrowserWindow } = electron;
const { isSameOrigin } = process.electronBinding('v8_util'); const { isSameOrigin } = process.electronBinding('v8_util');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils'); const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const parseFeaturesString = require('@electron/internal/common/parse-features-string'); const { convertFeaturesString } = require('@electron/internal/common/parse-features-string');
const hasProp = {}.hasOwnProperty; const hasProp = {}.hasOwnProperty;
const frameToGuest = new Map(); const frameToGuest = new Map();
@ -57,7 +57,11 @@ const mergeBrowserWindowOptions = function (embedder, options) {
// if parent's visibility is available, that overrides 'show' flag (#12125) // if parent's visibility is available, that overrides 'show' flag (#12125)
const win = BrowserWindow.fromWebContents(embedder.webContents); const win = BrowserWindow.fromWebContents(embedder.webContents);
if (win != null) { if (win != null) {
parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() }; parentOptions = {
...win.getBounds(),
...embedder.browserWindowOptions,
show: win.isVisible()
};
} }
// Inherit the original options if it is a BrowserWindow. // Inherit the original options if it is a BrowserWindow.
@ -83,6 +87,40 @@ const mergeBrowserWindowOptions = function (embedder, options) {
return options; return options;
}; };
const MULTIPART_CONTENT_TYPE = 'multipart/form-data';
const URL_ENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded';
function makeContentTypeHeader ({ contentType, boundary }) {
const header = `content-type: ${contentType};`;
if (contentType === MULTIPART_CONTENT_TYPE) {
return `${header} boundary=${boundary}`;
}
return header;
}
// Figure out appropriate headers for post data.
const parseContentTypeFormat = function (postData) {
if (postData.length) {
// For multipart forms, the first element will start with the boundary
// notice, which looks something like `------WebKitFormBoundary12345678`
// Note, this regex would fail when submitting a urlencoded form with an
// input attribute of name="--theKey", but, uhh, don't do that?
const postDataFront = postData[0].bytes.toString();
const boundary = /^--.*[^-\r\n]/.exec(postDataFront);
if (boundary) {
return {
boundary: boundary[0].substr(2),
contentType: MULTIPART_CONTENT_TYPE
};
}
}
// Either the form submission didn't contain any inputs (the postData array
// was empty), or we couldn't find the boundary and thus we can assume this is
// a key=value style form.
return {
contentType: URL_ENCODED_CONTENT_TYPE
};
};
// Setup a new guest with |embedder| // Setup a new guest with |embedder|
const setupGuest = function (embedder, frameName, guest, options) { const setupGuest = function (embedder, frameName, guest, options) {
// When |embedder| is destroyed we should also destroy attached guest, and if // When |embedder| is destroyed we should also destroy attached guest, and if
@ -134,14 +172,7 @@ const createGuest = function (embedder, url, referrer, frameName, options, postD
}; };
if (postData != null) { if (postData != null) {
loadOptions.postData = postData; loadOptions.postData = postData;
loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'; loadOptions.extraHeaders = makeContentTypeHeader(parseContentTypeFormat(postData));
if (postData.length > 0) {
const postDataFront = postData[0].bytes.toString();
const boundary = /^--.*[^-\r\n]/.exec(postDataFront);
if (boundary != null) {
loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`;
}
}
} }
guest.loadURL(url, loadOptions); guest.loadURL(url, loadOptions);
} }
@ -192,68 +223,21 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, fra
if (frameName == null) frameName = ''; if (frameName == null) frameName = '';
if (features == null) features = ''; if (features == null) features = '';
const options = {};
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'];
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag'];
const disposition = 'new-window'; const disposition = 'new-window';
const { options, additionalFeatures } = convertFeaturesString(features, frameName);
// Used to store additional features
const additionalFeatures = [];
// Parse the features
parseFeaturesString(features, function (key, value) {
if (value === undefined) {
additionalFeatures.push(key);
} else {
// Don't allow webPreferences to be set since it must be an object
// that cannot be directly overridden
if (key === 'webPreferences') return;
if (webPreferences.includes(key)) {
if (options.webPreferences == null) {
options.webPreferences = {};
}
options.webPreferences[key] = value;
} else {
options[key] = value;
}
}
});
if (options.left) {
if (options.x == null) {
options.x = options.left;
}
}
if (options.top) {
if (options.y == null) {
options.y = options.top;
}
}
if (options.title == null) {
options.title = frameName;
}
if (options.width == null) {
options.width = 800;
}
if (options.height == null) {
options.height = 600;
}
for (const name of ints) {
if (options[name] != null) {
options[name] = parseInt(options[name], 10);
}
}
const referrer = { url: '', policy: 'default' }; const referrer = { url: '', policy: 'default' };
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures); internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, null);
}); });
// Routed window.open messages with fully parsed options // Routed window.open messages with fully parsed options
function internalWindowOpen (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) { function internalWindowOpen (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) {
options = mergeBrowserWindowOptions(event.sender, options); options = mergeBrowserWindowOptions(event.sender, options);
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer); const postBody = postData ? {
data: postData,
...parseContentTypeFormat(postData)
} : null;
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer, postBody);
const { newGuest } = event; const { newGuest } = event;
if ((event.sender.getType() === 'webview' && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) { if ((event.sender.getType() === 'webview' && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) {
if (newGuest != null) { if (newGuest != null) {

View file

@ -3,7 +3,7 @@
// parses a feature string that has the format used in window.open() // parses a feature string that has the format used in window.open()
// - `features` input string // - `features` input string
// - `emit` function(key, value) - called for each parsed KV // - `emit` function(key, value) - called for each parsed KV
module.exports = function parseFeaturesString (features, emit) { function parseFeaturesString (features, emit) {
features = `${features}`.trim(); features = `${features}`.trim();
// split the string by ',' // split the string by ','
features.split(/\s*,\s*/).forEach((feature) => { features.split(/\s*,\s*/).forEach((feature) => {
@ -18,4 +18,68 @@ module.exports = function parseFeaturesString (features, emit) {
// emit the parsed pair // emit the parsed pair
emit(key, value); emit(key, value);
}); });
}
function convertFeaturesString (features, frameName) {
const options = {};
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'];
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag'];
// Used to store additional features
const additionalFeatures = [];
// Parse the features
parseFeaturesString(features, function (key, value) {
if (value === undefined) {
additionalFeatures.push(key);
} else {
// Don't allow webPreferences to be set since it must be an object
// that cannot be directly overridden
if (key === 'webPreferences') return;
if (webPreferences.includes(key)) {
if (options.webPreferences == null) {
options.webPreferences = {};
}
options.webPreferences[key] = value;
} else {
options[key] = value;
}
}
});
if (options.left) {
if (options.x == null) {
options.x = options.left;
}
}
if (options.top) {
if (options.y == null) {
options.y = options.top;
}
}
if (options.title == null) {
options.title = frameName;
}
if (options.width == null) {
options.width = 800;
}
if (options.height == null) {
options.height = 600;
}
for (const name of ints) {
if (options[name] != null) {
options[name] = parseInt(options[name], 10);
}
}
return {
options, additionalFeatures
};
}
module.exports = {
parseFeaturesString, convertFeaturesString
}; };

View file

@ -16,10 +16,27 @@ index 47749c430a71c3060c3e258ce1671bf6b03fa8c8..f2aedf5321a13742c6e3c2c4b7492b23
last_committed_origin_, params->window_container_type, last_committed_origin_, params->window_container_type,
params->target_url, params->referrer.To<Referrer>(), params->target_url, params->referrer.To<Referrer>(),
params->frame_name, params->disposition, *params->features, params->frame_name, params->disposition, *params->features,
+ params->additional_features, params->body, + params->raw_features, params->body,
effective_transient_activation_state, params->opener_suppressed, effective_transient_activation_state, params->opener_suppressed,
&no_javascript_access); &no_javascript_access);
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 4311fd59dfd8db1dfbebd898c43c3b48caf6cdd7..2ddb86702558920cb372d2c8b3423b2d53d68e56 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2910,9 +2910,9 @@ void WebContentsImpl::CreateNewWindow(
}
if (delegate_) {
- delegate_->WebContentsCreated(this, render_process_id,
- opener->GetRoutingID(), params.frame_name,
- params.target_url, new_contents_impl);
+ delegate_->WebContentsCreatedWithFullParams(this, render_process_id,
+ opener->GetRoutingID(),
+ params, new_contents_impl);
}
for (auto& observer : observers_) {
diff --git a/content/common/frame.mojom b/content/common/frame.mojom diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index 00d9c34304266b49e3f9703ef1aea6524d026ed5..44bb5868b8c3699230d74de0f0903af572d000fc 100644 index 00d9c34304266b49e3f9703ef1aea6524d026ed5..44bb5868b8c3699230d74de0f0903af572d000fc 100644
--- a/content/common/frame.mojom --- a/content/common/frame.mojom
@ -30,7 +47,7 @@ index 00d9c34304266b49e3f9703ef1aea6524d026ed5..44bb5868b8c3699230d74de0f0903af5
blink.mojom.WindowFeatures features; blink.mojom.WindowFeatures features;
+ +
+ // Extra fields added by Electron. + // Extra fields added by Electron.
+ array<string> additional_features; + string raw_features;
+ network.mojom.URLRequestBody? body; + network.mojom.URLRequestBody? body;
}; };
@ -43,7 +60,7 @@ index 702cd0792df47809d9278dd3af40e0e70494e229..75cd0ad7a8dc1efd89683d8da1920506
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features, const blink::mojom::WindowFeatures& features,
+ const std::vector<std::string>& additional_features, + const std::string& raw_features,
+ const scoped_refptr<network::ResourceRequestBody>& body, + const scoped_refptr<network::ResourceRequestBody>& body,
bool user_gesture, bool user_gesture,
bool opener_suppressed, bool opener_suppressed,
@ -64,11 +81,59 @@ index ed0460f7e3c9e2149eed89f7d35a4bb457f6b29f..5fc3d0e31927b618d04024fcc42b2061
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features, const blink::mojom::WindowFeatures& features,
+ const std::vector<std::string>& additional_features, + const std::string& raw_features,
+ const scoped_refptr<network::ResourceRequestBody>& body, + const scoped_refptr<network::ResourceRequestBody>& body,
bool user_gesture, bool user_gesture,
bool opener_suppressed, bool opener_suppressed,
bool* no_javascript_access); bool* no_javascript_access);
diff --git a/content/public/browser/web_contents_delegate.cc b/content/public/browser/web_contents_delegate.cc
index 5aa89aef4dbf3fe4ac7ce56b99c371d188584351..72a434fa612cdb4521aab981f98883af87783a80 100644
--- a/content/public/browser/web_contents_delegate.cc
+++ b/content/public/browser/web_contents_delegate.cc
@@ -26,6 +26,17 @@ namespace content {
WebContentsDelegate::WebContentsDelegate() = default;
+void WebContentsDelegate::WebContentsCreatedWithFullParams(
+ WebContents* source_contents,
+ int opener_render_process_id,
+ int opener_render_frame_id,
+ const mojom::CreateNewWindowParams& params,
+ WebContents* new_contents) {
+ WebContentsCreated(source_contents, opener_render_process_id,
+ opener_render_frame_id, params.frame_name,
+ params.target_url, new_contents);
+}
+
WebContents* WebContentsDelegate::OpenURLFromTab(WebContents* source,
const OpenURLParams& params) {
return nullptr;
diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h
index 01db3853cb915dca4873c779f06bbf84b002abf6..4a8e4cf2386594e27e2dd8117bac78531b28830c 100644
--- a/content/public/browser/web_contents_delegate.h
+++ b/content/public/browser/web_contents_delegate.h
@@ -16,6 +16,7 @@
#include "base/strings/string16.h"
#include "build/build_config.h"
#include "content/common/content_export.h"
+#include "content/common/frame.mojom.h"
#include "content/public/browser/bluetooth_chooser.h"
#include "content/public/browser/bluetooth_scanning_prompt.h"
#include "content/public/browser/invalidate_type.h"
@@ -333,6 +334,13 @@ class CONTENT_EXPORT WebContentsDelegate {
const std::string& partition_id,
SessionStorageNamespace* session_storage_namespace);
+ virtual void WebContentsCreatedWithFullParams(
+ WebContents* source_contents,
+ int opener_render_process_id,
+ int opener_render_frame_id,
+ const mojom::CreateNewWindowParams& params,
+ WebContents* new_contents);
+
// Notifies the delegate about the creation of a new WebContents. This
// typically happens when popups are created.
virtual void WebContentsCreated(WebContents* source_contents,
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index b3388258feb1d9fd791a975b08a382addd06af3a..98855151d1f07177a20b0f6079f2f48826d215ca 100644 index b3388258feb1d9fd791a975b08a382addd06af3a..98855151d1f07177a20b0f6079f2f48826d215ca 100644
--- a/content/renderer/render_view_impl.cc --- a/content/renderer/render_view_impl.cc
@ -81,10 +146,12 @@ index b3388258feb1d9fd791a975b08a382addd06af3a..98855151d1f07177a20b0f6079f2f488
#include "content/renderer/media/audio/audio_device_factory.h" #include "content/renderer/media/audio/audio_device_factory.h"
#include "content/renderer/render_frame_impl.h" #include "content/renderer/render_frame_impl.h"
#include "content/renderer/render_frame_proxy.h" #include "content/renderer/render_frame_proxy.h"
@@ -1255,6 +1256,8 @@ WebView* RenderViewImpl::CreateView( @@ -1255,6 +1256,10 @@ WebView* RenderViewImpl::CreateView(
} }
params->features = ConvertWebWindowFeaturesToMojoWindowFeatures(features); params->features = ConvertWebWindowFeaturesToMojoWindowFeatures(features);
+ params->raw_features = features.raw_features.Utf8(
+ WTF::UTF8ConversionMode::kStrictUTF8ConversionReplacingUnpairedSurrogatesWithFFFD);
+ params->body = GetRequestBodyForWebURLRequest(request); + params->body = GetRequestBodyForWebURLRequest(request);
+ +
// We preserve this information before sending the message since |params| is // We preserve this information before sending the message since |params| is
@ -98,7 +165,7 @@ index bf7c4bf0b76a89b1ecee6edc11fcfd06fe679968..79b2ba79653a251d3a4c3a54adbcd5c1
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features, const blink::mojom::WindowFeatures& features,
+ const std::vector<std::string>& additional_features, + const std::string& raw_features,
+ const scoped_refptr<network::ResourceRequestBody>& body, + const scoped_refptr<network::ResourceRequestBody>& body,
bool user_gesture, bool user_gesture,
bool opener_suppressed, bool opener_suppressed,
@ -111,8 +178,42 @@ index aaf3b8fed16bc2959941effaffd3aec87017ca59..1ea96df464f61c963a6d9aa224b9607e
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features, const blink::mojom::WindowFeatures& features,
+ const std::vector<std::string>& additional_features, + const std::string& raw_features,
+ const scoped_refptr<network::ResourceRequestBody>& body, + const scoped_refptr<network::ResourceRequestBody>& body,
bool user_gesture, bool user_gesture,
bool opener_suppressed, bool opener_suppressed,
bool* no_javascript_access) override; bool* no_javascript_access) override;
diff --git a/third_party/blink/public/web/web_window_features.h b/third_party/blink/public/web/web_window_features.h
index 4f735ad0d97eaac9a57dad137e479f8a7ec33a36..0a3c5821962c85609b64b3625fa6b8d658cd9ab2 100644
--- a/third_party/blink/public/web/web_window_features.h
+++ b/third_party/blink/public/web/web_window_features.h
@@ -31,6 +31,8 @@
#ifndef THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_WINDOW_FEATURES_H_
#define THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_WINDOW_FEATURES_H_
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
namespace blink {
struct WebWindowFeatures {
@@ -60,6 +62,8 @@ struct WebWindowFeatures {
bool noreferrer = false;
bool background = false;
bool persistent = false;
+
+ String raw_features;
};
} // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index 07cb6e3fabfa7a32f9923dcf4fbc8cebccdbde46..58e768bfcac2561f4427c71b6c31ba73f9be4b7b 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -1492,6 +1492,7 @@ DOMWindow* LocalDOMWindow::open(v8::Isolate* isolate,
}
WebWindowFeatures window_features = GetWindowFeaturesFromString(features);
+ window_features.raw_features = features;
FrameLoadRequest frame_request(active_document,
ResourceRequest(completed_url));

View file

@ -671,7 +671,7 @@ bool App::CanCreateWindow(
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features, const blink::mojom::WindowFeatures& features,
const std::vector<std::string>& additional_features, const std::string& raw_features,
const scoped_refptr<network::ResourceRequestBody>& body, const scoped_refptr<network::ResourceRequestBody>& body,
bool user_gesture, bool user_gesture,
bool opener_suppressed, bool opener_suppressed,
@ -685,7 +685,7 @@ bool App::CanCreateWindow(
// No need to emit any event if the WebContents is not available in JS. // No need to emit any event if the WebContents is not available in JS.
if (!api_web_contents.IsEmpty()) { if (!api_web_contents.IsEmpty()) {
api_web_contents->OnCreateWindow(target_url, referrer, frame_name, api_web_contents->OnCreateWindow(target_url, referrer, frame_name,
disposition, additional_features, body); disposition, raw_features, body);
} }
} }

View file

@ -131,7 +131,7 @@ class App : public ElectronBrowserClient::Delegate,
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features, const blink::mojom::WindowFeatures& features,
const std::vector<std::string>& additional_features, const std::string& raw_features,
const scoped_refptr<network::ResourceRequestBody>& body, const scoped_refptr<network::ResourceRequestBody>& body,
bool user_gesture, bool user_gesture,
bool opener_suppressed, bool opener_suppressed,

View file

@ -44,6 +44,7 @@
#include "content/public/browser/site_instance.h" #include "content/public/browser/site_instance.h"
#include "content/public/browser/storage_partition.h" #include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/common/referrer_type_converters.h"
#include "electron/buildflags/buildflags.h" #include "electron/buildflags/buildflags.h"
#include "electron/shell/common/api/api.mojom.h" #include "electron/shell/common/api/api.mojom.h"
#include "gin/data_object_builder.h" #include "gin/data_object_builder.h"
@ -641,25 +642,25 @@ void WebContents::OnCreateWindow(
const content::Referrer& referrer, const content::Referrer& referrer,
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const std::vector<std::string>& features, const std::string& features,
const scoped_refptr<network::ResourceRequestBody>& body) { const scoped_refptr<network::ResourceRequestBody>& body) {
if (type_ == Type::BROWSER_WINDOW || type_ == Type::OFF_SCREEN) Emit("-new-window", target_url, frame_name, disposition, features, referrer,
Emit("-new-window", target_url, frame_name, disposition, features, body, body);
referrer);
else
Emit("new-window", target_url, frame_name, disposition, features);
} }
void WebContents::WebContentsCreated(content::WebContents* source_contents, void WebContents::WebContentsCreatedWithFullParams(
int opener_render_process_id, content::WebContents* source_contents,
int opener_render_frame_id, int opener_render_process_id,
const std::string& frame_name, int opener_render_frame_id,
const GURL& target_url, const content::mojom::CreateNewWindowParams& params,
content::WebContents* new_contents) { content::WebContents* new_contents) {
ChildWebContentsTracker::CreateForWebContents(new_contents); ChildWebContentsTracker::CreateForWebContents(new_contents);
auto* tracker = ChildWebContentsTracker::FromWebContents(new_contents); auto* tracker = ChildWebContentsTracker::FromWebContents(new_contents);
tracker->url = target_url; tracker->url = params.target_url;
tracker->frame_name = frame_name; tracker->frame_name = params.frame_name;
tracker->referrer = params.referrer.To<content::Referrer>();
tracker->raw_features = params.raw_features;
tracker->body = params.body;
} }
void WebContents::AddNewContents( void WebContents::AddNewContents(
@ -678,7 +679,8 @@ void WebContents::AddNewContents(
CreateAndTake(isolate(), std::move(new_contents), Type::BROWSER_WINDOW); CreateAndTake(isolate(), std::move(new_contents), Type::BROWSER_WINDOW);
if (Emit("-add-new-contents", api_web_contents, disposition, user_gesture, if (Emit("-add-new-contents", api_web_contents, disposition, user_gesture,
initial_rect.x(), initial_rect.y(), initial_rect.width(), initial_rect.x(), initial_rect.y(), initial_rect.width(),
initial_rect.height(), tracker->url, tracker->frame_name)) { initial_rect.height(), tracker->url, tracker->frame_name,
tracker->referrer, tracker->raw_features, tracker->body)) {
// TODO(zcbenz): Can we make this sync? // TODO(zcbenz): Can we make this sync?
api_web_contents->DestroyWebContents(true /* async */); api_web_contents->DestroyWebContents(true /* async */);
} }
@ -688,10 +690,8 @@ content::WebContents* WebContents::OpenURLFromTab(
content::WebContents* source, content::WebContents* source,
const content::OpenURLParams& params) { const content::OpenURLParams& params) {
if (params.disposition != WindowOpenDisposition::CURRENT_TAB) { if (params.disposition != WindowOpenDisposition::CURRENT_TAB) {
if (type_ == Type::BROWSER_WINDOW || type_ == Type::OFF_SCREEN) Emit("-new-window", params.url, "", params.disposition, "", params.referrer,
Emit("-new-window", params.url, "", params.disposition); params.post_data);
else
Emit("new-window", params.url, "", params.disposition);
return nullptr; return nullptr;
} }

View file

@ -13,6 +13,7 @@
#include "base/observer_list.h" #include "base/observer_list.h"
#include "base/observer_list_types.h" #include "base/observer_list_types.h"
#include "content/common/cursors/webcursor.h" #include "content/common/cursors/webcursor.h"
#include "content/common/frame.mojom.h"
#include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/keyboard_event_processing_result.h" #include "content/public/browser/keyboard_event_processing_result.h"
#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host.h"
@ -312,7 +313,7 @@ class WebContents : public gin_helper::TrackableObject<WebContents>,
const content::Referrer& referrer, const content::Referrer& referrer,
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const std::vector<std::string>& features, const std::string& features,
const scoped_refptr<network::ResourceRequestBody>& body); const scoped_refptr<network::ResourceRequestBody>& body);
// Returns the preload script path of current WebContents. // Returns the preload script path of current WebContents.
@ -383,12 +384,12 @@ class WebContents : public gin_helper::TrackableObject<WebContents>,
const base::string16& message, const base::string16& message,
int32_t line_no, int32_t line_no,
const base::string16& source_id) override; const base::string16& source_id) override;
void WebContentsCreated(content::WebContents* source_contents, void WebContentsCreatedWithFullParams(
int opener_render_process_id, content::WebContents* source_contents,
int opener_render_frame_id, int opener_render_process_id,
const std::string& frame_name, int opener_render_frame_id,
const GURL& target_url, const content::mojom::CreateNewWindowParams& params,
content::WebContents* new_contents) override; content::WebContents* new_contents) override;
void AddNewContents(content::WebContents* source, void AddNewContents(content::WebContents* source,
std::unique_ptr<content::WebContents> new_contents, std::unique_ptr<content::WebContents> new_contents,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,

View file

@ -9,6 +9,8 @@ namespace electron {
ChildWebContentsTracker::ChildWebContentsTracker( ChildWebContentsTracker::ChildWebContentsTracker(
content::WebContents* web_contents) {} content::WebContents* web_contents) {}
ChildWebContentsTracker::~ChildWebContentsTracker() {}
WEB_CONTENTS_USER_DATA_KEY_IMPL(ChildWebContentsTracker) WEB_CONTENTS_USER_DATA_KEY_IMPL(ChildWebContentsTracker)
} // namespace electron } // namespace electron

View file

@ -15,8 +15,13 @@ namespace electron {
// created by native `window.open()` // created by native `window.open()`
struct ChildWebContentsTracker struct ChildWebContentsTracker
: public content::WebContentsUserData<ChildWebContentsTracker> { : public content::WebContentsUserData<ChildWebContentsTracker> {
~ChildWebContentsTracker() override;
GURL url; GURL url;
std::string frame_name; std::string frame_name;
content::Referrer referrer;
std::string raw_features;
scoped_refptr<network::ResourceRequestBody> body;
private: private:
explicit ChildWebContentsTracker(content::WebContents* web_contents); explicit ChildWebContentsTracker(content::WebContents* web_contents);

View file

@ -762,7 +762,7 @@ bool ElectronBrowserClient::CanCreateWindow(
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features, const blink::mojom::WindowFeatures& features,
const std::vector<std::string>& additional_features, const std::string& raw_features,
const scoped_refptr<network::ResourceRequestBody>& body, const scoped_refptr<network::ResourceRequestBody>& body,
bool user_gesture, bool user_gesture,
bool opener_suppressed, bool opener_suppressed,
@ -786,7 +786,7 @@ bool ElectronBrowserClient::CanCreateWindow(
return delegate_->CanCreateWindow( return delegate_->CanCreateWindow(
opener, opener_url, opener_top_level_frame_url, source_origin, opener, opener_url, opener_top_level_frame_url, source_origin,
container_type, target_url, referrer, frame_name, disposition, features, container_type, target_url, referrer, frame_name, disposition, features,
additional_features, body, user_gesture, opener_suppressed, raw_features, body, user_gesture, opener_suppressed,
no_javascript_access); no_javascript_access);
} }

View file

@ -128,7 +128,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
const std::string& frame_name, const std::string& frame_name,
WindowOpenDisposition disposition, WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features, const blink::mojom::WindowFeatures& features,
const std::vector<std::string>& additional_features, const std::string& raw_features,
const scoped_refptr<network::ResourceRequestBody>& body, const scoped_refptr<network::ResourceRequestBody>& body,
bool user_gesture, bool user_gesture,
bool opener_suppressed, bool opener_suppressed,

View file

@ -2,7 +2,7 @@ import { expect } from 'chai';
describe('feature-string parsing', () => { describe('feature-string parsing', () => {
it('is indifferent to whitespace around keys and values', () => { it('is indifferent to whitespace around keys and values', () => {
const parseFeaturesString = require('../lib/common/parse-features-string'); const { parseFeaturesString } = require('../lib/common/parse-features-string');
const checkParse = (string: string, parsed: Record<string, string | boolean>) => { const checkParse = (string: string, parsed: Record<string, string | boolean>) => {
const features: Record<string, string | boolean> = {}; const features: Record<string, string | boolean> = {};
parseFeaturesString(string, (k: string, v: any) => { features[k] = v; }); parseFeaturesString(string, (k: string, v: any) => { features[k] = v; });