diff --git a/lib/browser/parse-features-string.ts b/lib/browser/parse-features-string.ts index 4d54479c0438..be37505c64a0 100644 --- a/lib/browser/parse-features-string.ts +++ b/lib/browser/parse-features-string.ts @@ -26,7 +26,12 @@ const keysOfTypeNumberCompileTimeCheck: { [K in IntegerBrowserWindowOptionKeys] }; // Note `top` / `left` are special cases from the browser which we later convert // to y / x. -const keysOfTypeNumber = new Set(['top', 'left', ...Object.keys(keysOfTypeNumberCompileTimeCheck)]); +// NOTE(@mlaurencin) `innerWidth` / `innerHeight` are also special cases. The spec +// states that `width` and `height` represent the window content size and are equivalent +// to `innerWidth` / `innerHeight`. However, our implementation currently incorrectly maps +// `width` and `height` to `outerWidth` and `outerHeight`, or the size of the window +// with all border and related window chrome. +const keysOfTypeNumber = new Set(['top', 'left', 'innerWidth', 'innerHeight', ...Object.keys(keysOfTypeNumberCompileTimeCheck)]); /** * Note that we only allow "0" and "1" boolean conversion when the type is known diff --git a/shell/browser/native_window_mac.mm b/shell/browser/native_window_mac.mm index 51c1494ab8ff..8da2b2f36f62 100644 --- a/shell/browser/native_window_mac.mm +++ b/shell/browser/native_window_mac.mm @@ -120,8 +120,8 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options, ui::NativeTheme::GetInstanceForNativeUi()->AddObserver(this); display::Screen::GetScreen()->AddObserver(this); - const int width = options.ValueOrDefault(options::kWidth, 800); - const int height = options.ValueOrDefault(options::kHeight, 600); + int width = options.ValueOrDefault(options::kWidth, 800); + int height = options.ValueOrDefault(options::kHeight, 600); NSRect main_screen_rect = [[[NSScreen screens] firstObject] frame]; gfx::Rect bounds(round((NSWidth(main_screen_rect) - width) / 2), @@ -283,8 +283,23 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options, } // Resize to content bounds. - const bool use_content_size = + // NOTE(@mlaurencin) Spec requirements can be found here: + // https://developer.mozilla.org/en-US/docs/Web/API/Window/open#width + constexpr int kMinSizeReqdBySpec = 100; + int inner_width = 0; + int inner_height = 0; + bool use_content_size = options.ValueOrDefault(options::kUseContentSize, false); + options.Get(options::kinnerWidth, &inner_width); + options.Get(options::kinnerHeight, &inner_height); + if (inner_width || inner_height) { + use_content_size = true; + if (inner_width) + width = std::max(kMinSizeReqdBySpec, inner_width); + if (inner_height) + height = std::max(kMinSizeReqdBySpec, inner_height); + } + if (!has_frame() || use_content_size) SetContentSize(gfx::Size(width, height)); diff --git a/shell/browser/native_window_views.cc b/shell/browser/native_window_views.cc index 9898a4822c81..7f92eab063de 100644 --- a/shell/browser/native_window_views.cc +++ b/shell/browser/native_window_views.cc @@ -265,7 +265,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options, const int width = options.ValueOrDefault(options::kWidth, 800); const int height = options.ValueOrDefault(options::kHeight, 600); - const gfx::Rect bounds{0, 0, width, height}; + gfx::Rect bounds{0, 0, width, height}; widget_size_ = bounds.size(); widget()->AddObserver(this); @@ -406,10 +406,25 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options, // Default content view. SetContentView(new views::View()); + options.Get(options::kUseContentSize, &use_content_size_); + + // NOTE(@mlaurencin) Spec requirements can be found here: + // https://developer.mozilla.org/en-US/docs/Web/API/Window/open#width + int kMinSizeReqdBySpec = 100; + int inner_width = 0; + int inner_height = 0; + options.Get(options::kinnerWidth, &inner_width); + options.Get(options::kinnerHeight, &inner_height); + if (inner_width || inner_height) { + use_content_size_ = true; + if (inner_width) + bounds.set_width(std::max(kMinSizeReqdBySpec, inner_width)); + if (inner_height) + bounds.set_height(std::max(kMinSizeReqdBySpec, inner_height)); + } + gfx::Size size = bounds.size(); - if (has_frame() && - options.Get(options::kUseContentSize, &use_content_size_) && - use_content_size_) + if (has_frame() && use_content_size_) size = ContentBoundsToWindowBounds(gfx::Rect(size)).size(); widget()->CenterWindow(size); diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index 5bb2284acb4e..7b86b734e5a2 100644 --- a/shell/common/options_switches.h +++ b/shell/common/options_switches.h @@ -26,6 +26,8 @@ inline constexpr std::string_view kMinWidth = "minWidth"; inline constexpr std::string_view kMinHeight = "minHeight"; inline constexpr std::string_view kMaxWidth = "maxWidth"; inline constexpr std::string_view kMaxHeight = "maxHeight"; +inline constexpr std::string_view kinnerWidth = "innerWidth"; +inline constexpr std::string_view kinnerHeight = "innerHeight"; inline constexpr std::string_view kResizable = "resizable"; inline constexpr std::string_view kMovable = "movable"; inline constexpr std::string_view kMinimizable = "minimizable"; diff --git a/spec/chromium-spec.ts b/spec/chromium-spec.ts index 7aee5f66f3d9..90cfe8c8b92e 100644 --- a/spec/chromium-spec.ts +++ b/spec/chromium-spec.ts @@ -1459,6 +1459,41 @@ describe('chromium features', () => { expect(eventData).to.equal('size: 350 450'); }); + it('window opened with innerWidth option has the same innerWidth', async () => { + const w = new BrowserWindow({ show: false }); + w.loadFile(path.resolve(__dirname, 'fixtures', 'blank.html')); + const windowUrl = `file://${fixturesPath}/pages/window-open-size-inner.html`; + const windowCreatedPromise = once(app, 'browser-window-created') as Promise<[any, BrowserWindow]>; + const eventDataPromise = w.webContents.executeJavaScript(`(async () => { + const message = new Promise(resolve => window.addEventListener('message', resolve, { once: true })); + b = window.open(${JSON.stringify(windowUrl)}, '', 'show=no,innerWidth=400,height=450'); + const e = await message; + b.close(); + return e.data; + })()`); + const [[, newWindow], eventData] = await Promise.all([windowCreatedPromise, eventDataPromise]); + + expect(newWindow.getContentSize().toString()).to.equal('400,450'); + expect(eventData).to.equal('size: 400 450'); + }); + it('window opened with innerHeight option has the same innerHeight', async () => { + const w = new BrowserWindow({ show: false }); + w.loadFile(path.resolve(__dirname, 'fixtures', 'blank.html')); + const windowUrl = `file://${fixturesPath}/pages/window-open-size-inner.html`; + const windowCreatedPromise = once(app, 'browser-window-created') as Promise<[any, BrowserWindow]>; + const eventDataPromise = w.webContents.executeJavaScript(`(async () => { + const message = new Promise(resolve => window.addEventListener('message', resolve, {once: true})); + const b = window.open(${JSON.stringify(windowUrl)}, '', 'show=no,width=350,innerHeight=400') + const e = await message; + b.close(); + return e.data; + })()`); + const [[, newWindow], eventData] = await Promise.all([windowCreatedPromise, eventDataPromise]); + + expect(newWindow.getContentSize().toString()).to.equal('350,400'); + expect(eventData).to.equal('size: 350 400'); + }); + it('loads preload script after setting opener to null', async () => { const w = new BrowserWindow({ show: false }); w.webContents.setWindowOpenHandler(() => ({ diff --git a/spec/fixtures/pages/window-open-size-inner.html b/spec/fixtures/pages/window-open-size-inner.html new file mode 100644 index 000000000000..f06ea6351aa0 --- /dev/null +++ b/spec/fixtures/pages/window-open-size-inner.html @@ -0,0 +1,7 @@ + +
+ + +