blob: 466958f6df7a44648169afe3d668d6ccaba72932 [file] [log] [blame]
// Copyright 2018 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 file.
#include "ios/chrome/browser/ui/webui/inspect/inspect_ui.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#include "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/tabs/tab_model_list.h"
#import "ios/chrome/browser/tabs/tab_model_list_observer.h"
#include "ios/chrome/browser/web/java_script_console/java_script_console_message.h"
#include "ios/chrome/browser/web/java_script_console/java_script_console_tab_helper.h"
#include "ios/chrome/browser/web/java_script_console/java_script_console_tab_helper_delegate.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_list_observer.h"
#include "ios/chrome/grit/ios_resources.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ios/web/public/web_state/web_frame.h"
#import "ios/web/public/web_state/web_frames_manager.h"
#import "ios/web/public/web_state/web_state.h"
#include "ios/web/public/web_ui_ios_data_source.h"
#include "ios/web/public/webui/web_ui_ios.h"
#include "ios/web/public/webui/web_ui_ios_message_handler.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Used to record when the user loads the inspect page.
const char kInspectPageVisited[] = "IOSInspectPageVisited";
// The histogram used to record user actions performed on the inspect page.
const char kInspectConsoleHistogram[] = "IOS.Inspect.Console";
// Actions performed by the user logged to |kInspectConsoleHistogram|.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class InspectConsoleAction {
// Recorded when a user pressed the "Start Logging" button to collect logs.
kStartLogging = 0,
// Recorded when a user pressed the "Stop Logging" button.
kStopLogging = 1,
kMaxValue = kStopLogging,
};
web::WebUIIOSDataSource* CreateInspectUIHTMLSource() {
web::WebUIIOSDataSource* source =
web::WebUIIOSDataSource::Create(kChromeUIInspectHost);
source->AddLocalizedString("inspectConsoleNotice",
IDS_IOS_INSPECT_UI_CONSOLE_NOTICE);
source->AddLocalizedString("inspectConsoleStartLogging",
IDS_IOS_INSPECT_UI_CONSOLE_START_LOGGING);
source->AddLocalizedString("inspectConsoleStopLogging",
IDS_IOS_INSPECT_UI_CONSOLE_STOP_LOGGING);
source->SetJsonPath("strings.js");
source->AddResourcePath("inspect.js", IDR_IOS_INSPECT_JS);
source->SetDefaultResource(IDR_IOS_INSPECT_HTML);
source->UseGzip();
return source;
}
// The handler for Javascript messages for the chrome://inspect/ page.
class InspectDOMHandler : public web::WebUIIOSMessageHandler,
public JavaScriptConsoleTabHelperDelegate,
public TabModelListObserver,
public WebStateListObserver {
public:
InspectDOMHandler();
~InspectDOMHandler() override;
// WebUIIOSMessageHandler implementation
void RegisterMessages() override;
// JavaScriptConsoleTabHelperDelegate
void DidReceiveConsoleMessage(
web::WebState* web_state,
web::WebFrame* sender_frame,
const JavaScriptConsoleMessage& message) override;
// TabModelListObserver
void TabModelRegisteredWithBrowserState(
TabModel* tab_model,
ios::ChromeBrowserState* browser_state) override;
void TabModelUnregisteredFromBrowserState(
TabModel* tab_model,
ios::ChromeBrowserState* browser_state) override;
// WebStateListObserver
void WebStateInsertedAt(WebStateList* web_state_list,
web::WebState* web_state,
int index,
bool activating) override;
private:
// Handles the message from JavaScript to enable or disable console logging.
void HandleSetLoggingEnabled(const base::ListValue* args);
// Enables or disables console logging.
void SetLoggingEnabled(bool enabled);
// Sets |delegate| for the JavaScriptConsoleTabHelper associated with each web
// state in |tab_model|.
void SetDelegateForWebStatesInTabModel(
TabModel* tab_model,
JavaScriptConsoleTabHelperDelegate* delegate);
// Whether or not logging is enabled.
bool logging_enabled_ = false;
DISALLOW_COPY_AND_ASSIGN(InspectDOMHandler);
};
InspectDOMHandler::InspectDOMHandler() {}
InspectDOMHandler::~InspectDOMHandler() {
// Clear delegate from WebStates.
SetLoggingEnabled(false);
}
void InspectDOMHandler::HandleSetLoggingEnabled(const base::ListValue* args) {
DCHECK_EQ(1u, args->GetSize());
bool enabled = false;
if (!args->GetBoolean(0, &enabled)) {
NOTREACHED();
}
UMA_HISTOGRAM_ENUMERATION(kInspectConsoleHistogram,
enabled ? InspectConsoleAction::kStartLogging
: InspectConsoleAction::kStopLogging);
SetLoggingEnabled(enabled);
}
void InspectDOMHandler::SetLoggingEnabled(bool enabled) {
if (logging_enabled_ == enabled) {
return;
}
logging_enabled_ = enabled;
web::BrowserState* browser_state = web_ui()->GetWebState()->GetBrowserState();
ios::ChromeBrowserState* chrome_browser_state =
ios::ChromeBrowserState::FromBrowserState(browser_state);
NSArray<TabModel*>* tab_models =
TabModelList::GetTabModelsForChromeBrowserState(chrome_browser_state);
for (TabModel* tab_model in tab_models) {
if (enabled) {
tab_model.webStateList->AddObserver(this);
SetDelegateForWebStatesInTabModel(tab_model, this);
} else {
tab_model.webStateList->RemoveObserver(this);
SetDelegateForWebStatesInTabModel(tab_model, nullptr);
}
}
if (enabled) {
TabModelList::AddObserver(this);
} else {
TabModelList::RemoveObserver(this);
}
}
void InspectDOMHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"setLoggingEnabled",
base::BindRepeating(&InspectDOMHandler::HandleSetLoggingEnabled,
base::Unretained(this)));
}
void InspectDOMHandler::DidReceiveConsoleMessage(
web::WebState* web_state,
web::WebFrame* sender_frame,
const JavaScriptConsoleMessage& message) {
std::vector<base::Value> params;
web::WebFrame* main_web_frame =
web::WebFramesManager::FromWebState(web_state)->GetMainWebFrame();
params.push_back(base::Value(main_web_frame->GetFrameId()));
params.push_back(base::Value(sender_frame->GetFrameId()));
params.push_back(base::Value(message.url.spec()));
params.push_back(base::Value(message.level));
params.push_back(message.message->Clone());
web::WebFrame* inspect_ui_web_frame =
web::WebFramesManager::FromWebState(web_ui()->GetWebState())
->GetMainWebFrame();
inspect_ui_web_frame->CallJavaScriptFunction(
"inspectWebUI.logMessageReceived", params);
}
void InspectDOMHandler::SetDelegateForWebStatesInTabModel(
TabModel* tab_model,
JavaScriptConsoleTabHelperDelegate* delegate) {
for (int index = 0; index < tab_model.webStateList->count(); ++index) {
web::WebState* web_state = tab_model.webStateList->GetWebStateAt(index);
JavaScriptConsoleTabHelper::FromWebState(web_state)->SetDelegate(delegate);
}
}
// TabModelListObserver
void InspectDOMHandler::TabModelRegisteredWithBrowserState(
TabModel* tab_model,
ios::ChromeBrowserState* browser_state) {
tab_model.webStateList->AddObserver(this);
SetDelegateForWebStatesInTabModel(tab_model, this);
}
void InspectDOMHandler::TabModelUnregisteredFromBrowserState(
TabModel* tab_model,
ios::ChromeBrowserState* browser_state) {
tab_model.webStateList->RemoveObserver(this);
SetDelegateForWebStatesInTabModel(tab_model, nullptr);
}
// WebStateListObserver
void InspectDOMHandler::WebStateInsertedAt(WebStateList* web_state_list,
web::WebState* web_state,
int index,
bool activating) {
JavaScriptConsoleTabHelper::FromWebState(web_state)->SetDelegate(this);
}
} // namespace
InspectUI::InspectUI(web::WebUIIOS* web_ui) : web::WebUIIOSController(web_ui) {
base::RecordAction(base::UserMetricsAction(kInspectPageVisited));
web_ui->AddMessageHandler(std::make_unique<InspectDOMHandler>());
web::WebUIIOSDataSource::Add(ios::ChromeBrowserState::FromWebUIIOS(web_ui),
CreateInspectUIHTMLSource());
}
InspectUI::~InspectUI() {}