Compare commits

...

2 Commits

Author SHA1 Message Date
Shelley Vohr
36ce495886 feat: support chrome.tabs.onZoomChanged 2023-08-15 17:09:16 +02:00
Shelley Vohr
42391fa4af feat: support chrome.tabs.onUpdated 2023-08-15 16:56:53 +02:00
11 changed files with 425 additions and 5 deletions

View File

@@ -107,6 +107,11 @@ The following methods of `chrome.tabs` are supported:
- `chrome.tabs.update` (partial support)
- supported properties: `url`, `muted`.
The following events of `chrome.tabs` are supported:
- `chrome.tabs.onUpdated`
- `chrome.tabs.onZoomChanged`
> **Note:** In Chrome, passing `-1` as a tab ID signifies the "currently active
> tab". Since Electron has no such concept, passing `-1` as a tab ID is not
> supported and will raise an error.

View File

@@ -686,6 +686,10 @@ filenames = {
"shell/browser/extensions/api/streams_private/streams_private_api.h",
"shell/browser/extensions/api/tabs/tabs_api.cc",
"shell/browser/extensions/api/tabs/tabs_api.h",
"shell/browser/extensions/api/tabs/tabs_event_router.cc",
"shell/browser/extensions/api/tabs/tabs_event_router.h",
"shell/browser/extensions/api/tabs/tabs_window_api.cc",
"shell/browser/extensions/api/tabs/tabs_window_api.h",
"shell/browser/extensions/electron_browser_context_keyed_service_factories.cc",
"shell/browser/extensions/electron_browser_context_keyed_service_factories.h",
"shell/browser/extensions/electron_component_extension_resource_manager.cc",

View File

@@ -164,6 +164,8 @@
#include "extensions/browser/script_executor.h"
#include "extensions/browser/view_type_utils.h"
#include "extensions/common/mojom/view_type.mojom.h"
#include "shell/browser/extensions/api/tabs/tabs_event_router.h"
#include "shell/browser/extensions/api/tabs/tabs_window_api.h"
#include "shell/browser/extensions/electron_extension_web_contents_observer.h"
#endif
@@ -893,6 +895,14 @@ void WebContents::InitZoomController(content::WebContents* web_contents,
if (options.Get(options::kZoomFactor, &zoom_factor))
zoom_controller_->SetDefaultZoomFactor(zoom_factor);
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
auto* tabs_window_api = extensions::TabsWindowsAPI::Get(GetBrowserContext());
if (tabs_window_api) {
tabs_window_api->tabs_event_router()->RegisterForTabNotifications(
web_contents);
}
#endif
// Nothing to do with ZoomController, but this function gets called in all
// init cases!
content::RenderViewHost* host = web_contents->GetRenderViewHost();

View File

@@ -0,0 +1,126 @@
// Copyright (c) 2023 Microsoft, GmbH
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/extensions/api/tabs/tabs_event_router.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "chrome/browser/browser_process.h"
#include "components/zoom/zoom_controller.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/extensions/api/tabs/tabs_window_api.h"
#include "shell/browser/web_contents_zoom_controller.h"
#include "shell/common/extensions/api/tabs.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/page/page_zoom.h"
using base::Value;
using content::WebContents;
using zoom::ZoomController;
using electron::WebContentsZoomController;
namespace extensions {
// namespace {
// void ZoomModeToZoomSettings(ZoomController::ZoomMode zoom_mode,
// api::tabs::ZoomSettings* zoom_settings) {
// DCHECK(zoom_settings);
// switch (zoom_mode) {
// case ZoomController::ZOOM_MODE_DEFAULT:
// zoom_settings->mode = api::tabs::ZOOM_SETTINGS_MODE_AUTOMATIC;
// zoom_settings->scope = api::tabs::ZOOM_SETTINGS_SCOPE_PER_ORIGIN;
// break;
// case ZoomController::ZOOM_MODE_ISOLATED:
// zoom_settings->mode = api::tabs::ZOOM_SETTINGS_MODE_AUTOMATIC;
// zoom_settings->scope = api::tabs::ZOOM_SETTINGS_SCOPE_PER_TAB;
// break;
// case ZoomController::ZOOM_MODE_MANUAL:
// zoom_settings->mode = api::tabs::ZOOM_SETTINGS_MODE_MANUAL;
// zoom_settings->scope = api::tabs::ZOOM_SETTINGS_SCOPE_PER_TAB;
// break;
// case ZoomController::ZOOM_MODE_DISABLED:
// zoom_settings->mode = api::tabs::ZOOM_SETTINGS_MODE_DISABLED;
// zoom_settings->scope = api::tabs::ZOOM_SETTINGS_SCOPE_PER_TAB;
// break;
// }
// }
// } // namespace
TabsEventRouter::TabsEventRouter(content::BrowserContext* context)
: context_(context) {}
TabsEventRouter::~TabsEventRouter() = default;
void TabsEventRouter::OnZoomControllerDestroyed(
WebContentsZoomController* zoom_controller) {
if (zoom_scoped_observations_.IsObservingSource(zoom_controller)) {
zoom_scoped_observations_.RemoveObservation(zoom_controller);
}
}
void TabsEventRouter::OnZoomChanged(
const electron::WebContentsZoomController::ZoomChangedEventData& data) {
DCHECK(web_contents);
auto* api_web_contents = electron::api::WebContents::From(data.web_contents);
int tab_id = api_web_contents ? api_web_contents->ID() : -1;
if (tab_id < 0)
return;
// Prepare the zoom change information.
api::tabs::OnZoomChange::ZoomChangeInfo zoom_change_info;
zoom_change_info.tab_id = tab_id;
zoom_change_info.old_zoom_factor =
blink::PageZoomLevelToZoomFactor(data.old_zoom_level);
zoom_change_info.new_zoom_factor =
blink::PageZoomLevelToZoomFactor(data.new_zoom_level);
// ZoomModeToZoomSettings(data.zoom_mode, &zoom_change_info.zoom_settings);
// Dispatch the |onZoomChange| event.
DispatchEvent(data.web_contents->GetBrowserContext(),
events::TABS_ON_ZOOM_CHANGE,
api::tabs::OnZoomChange::kEventName,
api::tabs::OnZoomChange::Create(zoom_change_info),
EventRouter::USER_GESTURE_UNKNOWN);
}
void TabsEventRouter::DispatchEvent(
content::BrowserContext* context,
events::HistogramValue histogram_value,
const std::string& event_name,
base::Value::List args,
EventRouter::UserGestureState user_gesture) {
EventRouter* event_router = EventRouter::Get(context);
if (!event_router)
return;
auto event = std::make_unique<Event>(histogram_value, event_name,
std::move(args), context);
event->user_gesture = user_gesture;
event_router->BroadcastEvent(std::move(event));
}
void TabsEventRouter::RegisterForTabNotifications(WebContents* contents) {
zoom_scoped_observations_.AddObservation(
WebContentsZoomController::FromWebContents(contents));
}
void TabsEventRouter::UnregisterForTabNotifications(WebContents* contents) {
if (auto* zoom_controller =
WebContentsZoomController::FromWebContents(contents);
zoom_scoped_observations_.IsObservingSource(zoom_controller)) {
zoom_scoped_observations_.RemoveObservation(zoom_controller);
}
}
} // namespace extensions

View File

@@ -0,0 +1,74 @@
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_EXTENSIONS_API_TABS_TABS_EVENT_ROUTER_H_
#define SHELL_BROWSER_EXTENSIONS_API_TABS_TABS_EVENT_ROUTER_H_
#include <map>
#include <set>
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/scoped_multi_source_observation.h"
#include "base/scoped_observation.h"
#include "content/public/browser/web_contents_observer.h"
#include "extensions/browser/event_router.h"
#include "shell/browser/extensions/api/tabs/tabs_api.h"
#include "shell/browser/web_contents_zoom_controller.h"
#include "shell/browser/web_contents_zoom_observer.h"
namespace content {
class WebContents;
}
namespace extensions {
// The TabsEventRouter listens to tab events and routes them to listeners inside
// extension process renderers.
// TabsEventRouter will only route events from windows/tabs within a context to
// extension processes in the same context.
class TabsEventRouter : public electron::WebContentsZoomObserver {
public:
explicit TabsEventRouter(content::BrowserContext* context);
TabsEventRouter(const TabsEventRouter&) = delete;
TabsEventRouter& operator=(const TabsEventRouter&) = delete;
~TabsEventRouter() override;
// WebContentsZoomController::Observer
void OnZoomChanged(
const electron::WebContentsZoomController::ZoomChangedEventData& data)
override;
void OnZoomControllerDestroyed(
electron::WebContentsZoomController* controller) override;
// Register ourselves to receive the various notifications we are interested
// in for a tab. Also create tab entry to observe web contents notifications.
void RegisterForTabNotifications(content::WebContents* contents);
private:
// The DispatchEvent methods forward events to the |context|'s event router.
// The TabsEventRouter listens to events for all contexts,
// so we avoid duplication by dropping events destined for other contexts.
void DispatchEvent(content::BrowserContext* context,
events::HistogramValue histogram_value,
const std::string& event_name,
base::Value::List args,
EventRouter::UserGestureState user_gesture);
// Removes notifications and tab entry added in RegisterForTabNotifications.
void UnregisterForTabNotifications(content::WebContents* contents);
// The main context that owns this event router.
raw_ptr<content::BrowserContext> context_;
base::ScopedMultiSourceObservation<electron::WebContentsZoomController,
electron::WebContentsZoomObserver>
zoom_scoped_observations_{this};
};
} // namespace extensions
#endif // SHELL_BROWSER_EXTENSIONS_API_TABS_TABS_EVENT_ROUTER_H_

View File

@@ -0,0 +1,57 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/browser/extensions/api/tabs/tabs_window_api.h"
#include <memory>
#include "base/lazy_instance.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_system.h"
#include "shell/browser/extensions/api/tabs/tabs_event_router.h"
#include "shell/common/extensions/api/tabs.h"
namespace extensions {
TabsWindowsAPI::TabsWindowsAPI(content::BrowserContext* context)
: browser_context_(context) {
EventRouter* event_router = EventRouter::Get(browser_context_);
// Tabs API Events.
event_router->RegisterObserver(this, api::tabs::OnZoomChange::kEventName);
}
TabsWindowsAPI::~TabsWindowsAPI() = default;
// static
TabsWindowsAPI* TabsWindowsAPI::Get(content::BrowserContext* context) {
return BrowserContextKeyedAPIFactory<TabsWindowsAPI>::Get(context);
}
TabsEventRouter* TabsWindowsAPI::tabs_event_router() {
if (!tabs_event_router_.get()) {
tabs_event_router_ = std::make_unique<TabsEventRouter>(browser_context_);
}
return tabs_event_router_.get();
}
void TabsWindowsAPI::Shutdown() {
EventRouter::Get(browser_context_)->UnregisterObserver(this);
}
static base::LazyInstance<BrowserContextKeyedAPIFactory<TabsWindowsAPI>>::
DestructorAtExit g_tabs_windows_api_factory = LAZY_INSTANCE_INITIALIZER;
BrowserContextKeyedAPIFactory<TabsWindowsAPI>*
TabsWindowsAPI::GetFactoryInstance() {
return g_tabs_windows_api_factory.Pointer();
}
void TabsWindowsAPI::OnListenerAdded(const EventListenerInfo& details) {
// Initialize the event routers.
tabs_event_router();
EventRouter::Get(browser_context_)->UnregisterObserver(this);
}
} // namespace extensions

View File

@@ -0,0 +1,53 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_EXTENSIONS_API_TABS_TABS_WINDOWS_API_H_
#define SHELL_BROWSER_EXTENSIONS_API_TABS_TABS_WINDOWS_API_H_
#include <memory>
#include "base/memory/raw_ptr.h"
#include "components/keyed_service/core/keyed_service.h"
#include "extensions/browser/browser_context_keyed_api_factory.h"
#include "extensions/browser/event_router.h"
namespace extensions {
class TabsEventRouter;
class TabsWindowsAPI : public BrowserContextKeyedAPI,
public EventRouter::Observer {
public:
explicit TabsWindowsAPI(content::BrowserContext* context);
~TabsWindowsAPI() override;
// Convenience method to get the TabsWindowsAPI for a BrowserContext.
static TabsWindowsAPI* Get(content::BrowserContext* context);
TabsEventRouter* tabs_event_router();
// KeyedService implementation.
void Shutdown() override;
// BrowserContextKeyedAPI implementation.
static BrowserContextKeyedAPIFactory<TabsWindowsAPI>* GetFactoryInstance();
// EventRouter::Observer implementation.
void OnListenerAdded(const extensions::EventListenerInfo& details) override;
private:
friend class BrowserContextKeyedAPIFactory<TabsWindowsAPI>;
raw_ptr<content::BrowserContext> browser_context_;
// BrowserContextKeyedAPI implementation.
static const char* service_name() {
return "TabsWindowsAPI";
}
std::unique_ptr<TabsEventRouter> tabs_event_router_;
};
} // namespace extensions
#endif // SHELL_BROWSER_EXTENSIONS_API_TABS_TABS_WINDOWS_API_H_

View File

@@ -5,6 +5,7 @@
#include "shell/browser/extensions/electron_browser_context_keyed_service_factories.h"
#include "extensions/browser/updater/update_service_factory.h"
#include "shell/browser/extensions/api/tabs/tabs_window_api.h"
#include "shell/browser/extensions/electron_extension_system_factory.h"
namespace extensions::electron {
@@ -14,6 +15,8 @@ void EnsureBrowserContextKeyedServiceFactoriesBuilt() {
// extensions embedders (and namely chrome.)
UpdateServiceFactory::GetInstance();
TabsWindowsAPI::GetFactoryInstance();
ElectronExtensionSystemFactory::GetInstance();
}

View File

@@ -705,6 +705,90 @@
}
],
"events": [
// Electron does not support tabs.create, so this event will never be fired,
// but we have this here to prevent it from causing undefined errors.
{
"name": "onCreated",
"type": "function",
"description": "Fired when a tab is created. Note that the tab's URL and tab group membership may not be set at the time this event is fired, but you can listen to onUpdated events so as to be notified when a URL is set or the tab is added to a tab group.",
"parameters": [
{
"$ref": "Tab",
"name": "tab",
"description": "Details of the tab that was created."
}
]
},
{
"name": "onUpdated",
"type": "function",
"description": "Fired when a tab is updated.",
"parameters": [
{
"type": "integer",
"name": "tabId",
"minimum": 0
},
{
"type": "object",
"name": "changeInfo",
"description": "Lists the changes to the state of the tab that was updated.",
"properties": {
"url": {
"type": "string",
"optional": true,
"description": "The tab's URL if it has changed."
},
"groupId": {
"type": "integer",
"optional": true,
"minimum": -1,
"description": "The tab's new group."
},
"pinned": {
"type": "boolean",
"optional": true,
"description": "The tab's new pinned state."
},
"audible": {
"type": "boolean",
"optional": true,
"description": "The tab's new audible state."
},
"discarded": {
"type": "boolean",
"optional": true,
"description": "The tab's new discarded state."
},
"autoDiscardable": {
"type": "boolean",
"optional": true,
"description": "The tab's new auto-discardable state."
},
"mutedInfo": {
"$ref": "MutedInfo",
"optional": true,
"description": "The tab's new muted state and the reason for the change."
},
"favIconUrl": {
"type": "string",
"optional": true,
"description": "The tab's new favicon URL."
},
"title": {
"type": "string",
"optional": true,
"description": "The tab's new title."
}
}
},
{
"$ref": "Tab",
"name": "tab",
"description": "Gives the state of the tab that was updated."
}
]
},
{
"name": "onZoomChange",
"type": "function",

View File

@@ -878,7 +878,12 @@ describe('chrome extensions', () => {
const [,, responseString] = await once(w.webContents, 'console-message');
const response = JSON.parse(responseString);
expect(response).to.deep.equal(2);
expect(response).to.deep.equal({
newZoomFactor: 2,
oldZoomFactor: 1.2,
tabId: 1,
zoomSettings: {}
});
});
it('getZoomSettings', async () => {

View File

@@ -12,10 +12,9 @@ const handleRequest = (request, sender, sendResponse) => {
case 'setZoom': {
const [zoom] = args;
chrome.tabs.setZoom(tabId, zoom).then(async () => {
const updatedZoom = await chrome.tabs.getZoom(tabId);
sendResponse(updatedZoom);
});
chrome.tabs.onZoomChange.addListener(sendResponse);
chrome.tabs.setZoom(tabId, zoom);
break;
}