Browse Source

Merge pull request #12637 from electron/expose-toplevel-window

Refactor NativeWindow (Part 7): Expose TopLevelWindow in JavaScript
Cheng Zhao 7 years ago
parent
commit
6c9f3066fd

+ 2 - 9
atom/browser/api/atom_api_browser_window.cc

@@ -29,7 +29,7 @@ namespace api {
 BrowserWindow::BrowserWindow(v8::Isolate* isolate,
                              v8::Local<v8::Object> wrapper,
                              const mate::Dictionary& options)
-    : TopLevelWindow(isolate, wrapper, options), weak_factory_(this) {
+    : TopLevelWindow(isolate, options), weak_factory_(this) {
   mate::Handle<class WebContents> web_contents;
 
   // Use options.webPreferences in WebContents.
@@ -397,7 +397,6 @@ mate::WrappableBase* BrowserWindow::New(mate::Arguments* args) {
 // static
 void BrowserWindow::BuildPrototype(v8::Isolate* isolate,
                                    v8::Local<v8::FunctionTemplate> prototype) {
-  TopLevelWindow::BuildPrototype(isolate, prototype);
   prototype->SetClassName(mate::StringToV8(isolate, "BrowserWindow"));
   mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
       .SetMethod("focusOnWebView", &BrowserWindow::FocusOnWebView)
@@ -439,14 +438,8 @@ void Initialize(v8::Local<v8::Object> exports,
   templ->InstanceTemplate()->SetInternalFieldCount(1);
   BrowserWindow::BuildPrototype(isolate, templ);
 
-  mate::Dictionary browser_window(isolate, templ->GetFunction());
-  browser_window.SetMethod(
-      "fromId", &mate::TrackableObject<TopLevelWindow>::FromWeakMapID);
-  browser_window.SetMethod("getAllWindows",
-                           &mate::TrackableObject<TopLevelWindow>::GetAll);
-
   mate::Dictionary dict(isolate, exports);
-  dict.Set("BrowserWindow", browser_window);
+  dict.Set("BrowserWindow", templ->GetFunction());
 }
 
 }  // namespace

+ 0 - 23
atom/browser/api/atom_api_browser_window.h

@@ -118,27 +118,4 @@ class BrowserWindow : public TopLevelWindow,
 
 }  // namespace atom
 
-namespace mate {
-
-template <>
-struct Converter<atom::NativeWindow*> {
-  static bool FromV8(v8::Isolate* isolate,
-                     v8::Local<v8::Value> val,
-                     atom::NativeWindow** out) {
-    // null would be tranfered to NULL.
-    if (val->IsNull()) {
-      *out = NULL;
-      return true;
-    }
-
-    atom::api::BrowserWindow* window;
-    if (!Converter<atom::api::BrowserWindow*>::FromV8(isolate, val, &window))
-      return false;
-    *out = window->window();
-    return true;
-  }
-};
-
-}  // namespace mate
-
 #endif  // ATOM_BROWSER_API_ATOM_API_BROWSER_WINDOW_H_

+ 2 - 2
atom/browser/api/atom_api_menu.h

@@ -8,7 +8,7 @@
 #include <memory>
 #include <string>
 
-#include "atom/browser/api/atom_api_browser_window.h"
+#include "atom/browser/api/atom_api_top_level_window.h"
 #include "atom/browser/api/trackable_object.h"
 #include "atom/browser/ui/atom_menu_model.h"
 #include "base/callback.h"
@@ -54,7 +54,7 @@ class Menu : public mate::TrackableObject<Menu>,
   void ExecuteCommand(int command_id, int event_flags) override;
   void MenuWillShow(ui::SimpleMenuModel* source) override;
 
-  virtual void PopupAt(BrowserWindow* window,
+  virtual void PopupAt(TopLevelWindow* window,
                        int x,
                        int y,
                        int positioning_item,

+ 1 - 1
atom/browser/api/atom_api_menu_mac.h

@@ -22,7 +22,7 @@ class MenuMac : public Menu {
  protected:
   MenuMac(v8::Isolate* isolate, v8::Local<v8::Object> wrapper);
 
-  void PopupAt(BrowserWindow* window,
+  void PopupAt(TopLevelWindow* window,
                int x,
                int y,
                int positioning_item,

+ 1 - 3
atom/browser/api/atom_api_menu_mac.mm

@@ -9,8 +9,6 @@
 #include "base/mac/scoped_sending_event.h"
 #include "base/message_loop/message_loop.h"
 #include "base/strings/sys_string_conversions.h"
-#include "brightray/browser/inspectable_web_contents.h"
-#include "brightray/browser/inspectable_web_contents_view.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
 
@@ -27,7 +25,7 @@ MenuMac::MenuMac(v8::Isolate* isolate, v8::Local<v8::Object> wrapper)
       weak_factory_(this) {
 }
 
-void MenuMac::PopupAt(BrowserWindow* window,
+void MenuMac::PopupAt(TopLevelWindow* window,
                       int x, int y, int positioning_item,
                       const base::Closure& callback) {
   NativeWindow* native_window = window->window();

+ 1 - 3
atom/browser/api/atom_api_menu_views.cc

@@ -6,8 +6,6 @@
 
 #include "atom/browser/native_window_views.h"
 #include "atom/browser/unresponsive_suppressor.h"
-#include "brightray/browser/inspectable_web_contents.h"
-#include "brightray/browser/inspectable_web_contents_view.h"
 #include "ui/display/screen.h"
 
 using views::MenuRunner;
@@ -19,7 +17,7 @@ namespace api {
 MenuViews::MenuViews(v8::Isolate* isolate, v8::Local<v8::Object> wrapper)
     : Menu(isolate, wrapper), weak_factory_(this) {}
 
-void MenuViews::PopupAt(BrowserWindow* window,
+void MenuViews::PopupAt(TopLevelWindow* window,
                         int x,
                         int y,
                         int positioning_item,

+ 1 - 1
atom/browser/api/atom_api_menu_views.h

@@ -21,7 +21,7 @@ class MenuViews : public Menu {
   MenuViews(v8::Isolate* isolate, v8::Local<v8::Object> wrapper);
 
  protected:
-  void PopupAt(BrowserWindow* window,
+  void PopupAt(TopLevelWindow* window,
                int x,
                int y,
                int positioning_item,

+ 24 - 7
atom/browser/api/atom_api_top_level_window.cc

@@ -9,6 +9,7 @@
 
 #include "atom/browser/api/atom_api_browser_view.h"
 #include "atom/browser/api/atom_api_menu.h"
+#include "atom/browser/api/atom_api_web_contents.h"
 #include "atom/common/color_util.h"
 #include "atom/common/native_mate_converters/callback.h"
 #include "atom/common/native_mate_converters/file_path_converter.h"
@@ -70,7 +71,6 @@ v8::Local<v8::Value> ToBuffer(v8::Isolate* isolate, void* val, int size) {
 }  // namespace
 
 TopLevelWindow::TopLevelWindow(v8::Isolate* isolate,
-                               v8::Local<v8::Object> wrapper,
                                const mate::Dictionary& options)
     : weak_factory_(this) {
   // The parent window.
@@ -99,13 +99,15 @@ TopLevelWindow::TopLevelWindow(v8::Isolate* isolate,
   if (options.Get(options::kIcon, &icon) && !icon.IsEmpty())
     SetIcon(icon);
 #endif
+}
 
-  AttachAsUserData(window_.get());
-
-  // We can only append this window to parent window's child windows after this
-  // window's JS wrapper gets initialized.
-  if (!parent.IsEmpty())
-    parent->child_windows_.Set(isolate, weak_map_id(), wrapper);
+TopLevelWindow::TopLevelWindow(v8::Isolate* isolate,
+                               v8::Local<v8::Object> wrapper,
+                               const mate::Dictionary& options)
+    : TopLevelWindow(isolate, options) {
+  InitWith(isolate, wrapper);
+  // Init window after everything has been setup.
+  window()->InitFromOptions(options);
 }
 
 TopLevelWindow::~TopLevelWindow() {
@@ -117,6 +119,21 @@ TopLevelWindow::~TopLevelWindow() {
   base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, window_.release());
 }
 
+void TopLevelWindow::InitWith(v8::Isolate* isolate,
+                              v8::Local<v8::Object> wrapper) {
+  AttachAsUserData(window_.get());
+  mate::TrackableObject<TopLevelWindow>::InitWith(isolate, wrapper);
+
+  // We can only append this window to parent window's child windows after this
+  // window's JS wrapper gets initialized.
+  if (!parent_window_.IsEmpty()) {
+    mate::Handle<TopLevelWindow> parent;
+    mate::ConvertFromV8(isolate, GetParentWindow(), &parent);
+    DCHECK(!parent.IsEmpty());
+    parent->child_windows_.Set(isolate, weak_map_id(), wrapper);
+  }
+}
+
 void TopLevelWindow::WillCloseWindow(bool* prevent_default) {
   *prevent_default = Emit("close");
 }

+ 29 - 0
atom/browser/api/atom_api_top_level_window.h

@@ -35,11 +35,17 @@ class TopLevelWindow : public mate::TrackableObject<TopLevelWindow>,
   NativeWindow* window() const { return window_.get(); }
 
  protected:
+  // Common constructor.
+  TopLevelWindow(v8::Isolate* isolate, const mate::Dictionary& options);
+  // Creating independent TopLevelWindow instance.
   TopLevelWindow(v8::Isolate* isolate,
                  v8::Local<v8::Object> wrapper,
                  const mate::Dictionary& options);
   ~TopLevelWindow() override;
 
+  // TrackableObject:
+  void InitWith(v8::Isolate* isolate, v8::Local<v8::Object> wrapper) override;
+
   // NativeWindowObserver:
   void WillCloseWindow(bool* prevent_default) override;
   void OnWindowClosed() override;
@@ -223,4 +229,27 @@ class TopLevelWindow : public mate::TrackableObject<TopLevelWindow>,
 
 }  // namespace atom
 
+namespace mate {
+
+template<>
+struct Converter<atom::NativeWindow*> {
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     atom::NativeWindow** out) {
+    // null would be tranfered to NULL.
+    if (val->IsNull()) {
+      *out = NULL;
+      return true;
+    }
+
+    atom::api::TopLevelWindow* window;
+    if (!Converter<atom::api::TopLevelWindow*>::FromV8(isolate, val, &window))
+      return false;
+    *out = window->window();
+    return true;
+  }
+};
+
+}  // namespace mate
+
 #endif  // ATOM_BROWSER_API_ATOM_API_TOP_LEVEL_WINDOW_H_

+ 1 - 0
filenames.gypi

@@ -34,6 +34,7 @@
       'lib/browser/api/screen.js',
       'lib/browser/api/session.js',
       'lib/browser/api/system-preferences.js',
+      'lib/browser/api/top-level-window.js',
       'lib/browser/api/touch-bar.js',
       'lib/browser/api/tray.js',
       'lib/browser/api/web-contents.js',

+ 19 - 10
lib/browser/api/browser-window.js

@@ -1,22 +1,18 @@
 'use strict'
 
 const electron = require('electron')
-const {ipcMain} = electron
-const {EventEmitter} = require('events')
+const {ipcMain, TopLevelWindow} = electron
 const {BrowserWindow} = process.atomBinding('window')
 const v8Util = process.atomBinding('v8_util')
 
-Object.setPrototypeOf(BrowserWindow.prototype, EventEmitter.prototype)
+Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype)
 
 BrowserWindow.prototype._init = function () {
-  // Avoid recursive require.
-  const {app} = require('electron')
+  // Call parent class's _init.
+  TopLevelWindow.prototype._init.call(this)
 
-  // Simulate the application menu on platforms other than macOS.
-  if (process.platform !== 'darwin') {
-    const menu = app.getApplicationMenu()
-    if (menu) this.setMenu(menu)
-  }
+  // Avoid recursive require.
+  const {app} = electron
 
   // Make new windows requested by links behave like "window.open"
   this.webContents.on('-new-window', (event, url, frameName, disposition,
@@ -132,6 +128,19 @@ BrowserWindow.prototype._init = function () {
   })
 }
 
+const isBrowserWindow = (win) => {
+  return win && win.constructor.name === 'BrowserWindow'
+}
+
+BrowserWindow.fromId = (id) => {
+  const win = TopLevelWindow.fromId(id)
+  return isBrowserWindow(win) ? win : null
+}
+
+BrowserWindow.getAllWindows = () => {
+  return TopLevelWindow.getAllWindows().filter(isBrowserWindow)
+}
+
 BrowserWindow.getFocusedWindow = () => {
   for (let window of BrowserWindow.getAllWindows()) {
     if (window.isFocused() || window.isDevToolsFocused()) return window

+ 7 - 7
lib/browser/api/menu.js

@@ -1,6 +1,6 @@
 'use strict'
 
-const {BrowserWindow, MenuItem, webContents} = require('electron')
+const {TopLevelWindow, MenuItem, webContents} = require('electron')
 const EventEmitter = require('events').EventEmitter
 const v8Util = process.atomBinding('v8_util')
 const bindings = process.atomBinding('menu')
@@ -26,7 +26,7 @@ const delegate = {
   executeCommand: (menu, event, id) => {
     const command = menu.commandsMap[id]
     if (!command) return
-    command.click(event, BrowserWindow.getFocusedWindow(), webContents.getFocusedWebContents())
+    command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents())
   },
   menuWillShow: (menu) => {
     // Ensure radio groups have at least one menu item seleted
@@ -61,14 +61,14 @@ Menu.prototype.popup = function (options) {
   if (typeof positioningItem !== 'number') positioningItem = -1
 
   // find which window to use
-  const wins = BrowserWindow.getAllWindows()
+  const wins = TopLevelWindow.getAllWindows()
   if (!wins || wins.indexOf(window) === -1) {
-    window = BrowserWindow.getFocusedWindow()
+    window = TopLevelWindow.getFocusedWindow()
     if (!window && wins && wins.length > 0) {
       window = wins[0]
     }
     if (!window) {
-      throw new Error(`Cannot open Menu without a BrowserWindow present`)
+      throw new Error(`Cannot open Menu without a TopLevelWindow present`)
     }
   }
 
@@ -77,7 +77,7 @@ Menu.prototype.popup = function (options) {
 }
 
 Menu.prototype.closePopup = function (window) {
-  if (window && window.constructor !== BrowserWindow) {
+  if (window instanceof TopLevelWindow) {
     this.closePopupAt(window.id)
   } else {
     // Passing -1 (invalid) would make closePopupAt close the all menu runners
@@ -148,7 +148,7 @@ Menu.setApplicationMenu = function (menu) {
     menu._callMenuWillShow()
     bindings.setApplicationMenu(menu)
   } else {
-    const windows = BrowserWindow.getAllWindows()
+    const windows = TopLevelWindow.getAllWindows()
     return windows.map(w => w.setMenu(menu))
   }
 }

+ 1 - 0
lib/browser/api/module-list.js

@@ -19,6 +19,7 @@ module.exports = [
   {name: 'screen', file: 'screen'},
   {name: 'session', file: 'session'},
   {name: 'systemPreferences', file: 'system-preferences'},
+  {name: 'TopLevelWindow', file: 'top-level-window'},
   {name: 'TouchBar', file: 'touch-bar'},
   {name: 'Tray', file: 'tray'},
   {name: 'webContents', file: 'web-contents'},

+ 24 - 0
lib/browser/api/top-level-window.js

@@ -0,0 +1,24 @@
+'use strict'
+
+const electron = require('electron')
+const {EventEmitter} = require('events')
+const {TopLevelWindow} = process.atomBinding('top_level_window')
+
+Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype)
+
+TopLevelWindow.prototype._init = function () {
+  // Avoid recursive require.
+  const {app} = electron
+
+  // Simulate the application menu on platforms other than macOS.
+  if (process.platform !== 'darwin') {
+    const menu = app.getApplicationMenu()
+    if (menu) this.setMenu(menu)
+  }
+}
+
+TopLevelWindow.getFocusedWindow = () => {
+  return TopLevelWindow.getAllWindows().find((win) => win.isFocused())
+}
+
+module.exports = TopLevelWindow