Commit c9465362 authored by Pavel Feldman's avatar Pavel Feldman Committed by Commit Bot

DevTools: introduce Page.getInstallabilityErrors based on InstallableManager.

... and migrate front-end to it.

Bug: 915945
Change-Id: I91220f7d7681fef3faa4c543b729d3475df5b8ce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1560533
Commit-Queue: Pavel Feldman <pfeldman@chromium.org>
Reviewed-by: default avatarJoel Einbinder <einbinder@chromium.org>
Cr-Commit-Position: refs/heads/master@{#649761}
parent 2d863ab1
......@@ -8,8 +8,9 @@
"options": [
{
"domain": "Page",
"include": [ "enable", "disable", "setAdBlockingEnabled" ],
"include_events": []
"include": [ "enable", "disable", "setAdBlockingEnabled", "getInstallabilityErrors" ],
"include_events": [],
"async": ["getInstallabilityErrors"]
},
{
"domain": "Browser",
......
......@@ -4,6 +4,7 @@
#include "chrome/browser/devtools/protocol/page_handler.h"
#include "chrome/browser/installable/installable_manager.h"
#include "chrome/browser/subresource_filter/chrome_subresource_filter_client.h"
PageHandler::PageHandler(content::WebContents* web_contents,
......@@ -47,3 +48,28 @@ protocol::Response PageHandler::SetAdBlockingEnabled(bool enabled) {
ToggleAdBlocking(enabled);
return protocol::Response::OK();
}
void PageHandler::GetInstallabilityErrors(
std::unique_ptr<GetInstallabilityErrorsCallback> callback) {
auto errors = protocol::Array<std::string>::create();
InstallableManager* manager =
web_contents() ? InstallableManager::FromWebContents(web_contents())
: nullptr;
if (!manager) {
callback->sendFailure(
protocol::Response::Error("Unable to fetch errors for target"));
return;
}
manager->GetAllErrors(base::BindOnce(&PageHandler::GotInstallabilityErrors,
std::move(callback)));
}
// static
void PageHandler::GotInstallabilityErrors(
std::unique_ptr<GetInstallabilityErrorsCallback> callback,
std::vector<std::string> errors) {
auto result = protocol::Array<std::string>::create();
for (const auto& error : errors)
result->addItem(error);
callback->sendSuccess(std::move(result));
}
......@@ -26,8 +26,14 @@ class PageHandler : public protocol::Page::Backend,
protocol::Response Enable() override;
protocol::Response Disable() override;
protocol::Response SetAdBlockingEnabled(bool enabled) override;
void GetInstallabilityErrors(
std::unique_ptr<GetInstallabilityErrorsCallback> callback) override;
private:
static void GotInstallabilityErrors(
std::unique_ptr<GetInstallabilityErrorsCallback> callback,
std::vector<std::string> errors);
bool enabled_ = false;
DISALLOW_COPY_AND_ASSIGN(PageHandler);
......
......@@ -1155,5 +1155,13 @@ Response PageHandler::SetWebLifecycleState(const std::string& state) {
return Response::Error("Unidentified lifecycle state");
}
void PageHandler::GetInstallabilityErrors(
std::unique_ptr<GetInstallabilityErrorsCallback> callback) {
auto errors = protocol::Array<std::string>::create();
// TODO: Use InstallableManager once it moves into content/.
// Until then, this code is only used to return empty array in the tests.
callback->sendSuccess(std::move(errors));
}
} // namespace protocol
} // namespace content
......@@ -155,6 +155,8 @@ class PageHandler : public DevToolsDomainHandler,
std::unique_ptr<GetAppManifestCallback> callback) override;
Response SetWebLifecycleState(const std::string& state) override;
void GetInstallabilityErrors(
std::unique_ptr<GetInstallabilityErrorsCallback> callback) override;
private:
enum EncodingFormat { PNG, JPEG };
......
......@@ -57,9 +57,9 @@
"domain": "Page",
"include": ["enable", "disable", "reload", "navigate", "stopLoading", "getNavigationHistory", "navigateToHistoryEntry", "resetNavigationHistory", "captureScreenshot",
"startScreencast", "stopScreencast", "screencastFrameAck", "handleJavaScriptDialog", "setColorPickerEnabled",
"printToPDF", "bringToFront", "setDownloadBehavior", "getAppManifest", "crash", "close", "setWebLifecycleState", "captureSnapshot"],
"printToPDF", "bringToFront", "setDownloadBehavior", "getAppManifest", "crash", "close", "setWebLifecycleState", "captureSnapshot", "getInstallabilityErrors"],
"include_events": ["colorPicked", "interstitialShown", "interstitialHidden", "javascriptDialogOpening", "javascriptDialogClosed", "screencastVisibilityChanged", "screencastFrame"],
"async": ["captureScreenshot", "printToPDF", "navigate", "getAppManifest", "reload", "captureSnapshot"]
"async": ["captureScreenshot", "printToPDF", "navigate", "getAppManifest", "reload", "captureSnapshot", "getInstallabilityErrors"]
},
{
"domain": "Runtime",
......
......@@ -5173,6 +5173,10 @@ domain Page
# Manifest content.
optional string data
experimental command getInstallabilityErrors
returns
array of string errors
# Returns all browser cookies. Depending on the backend support, will return detailed cookie
# information in the `cookies` field.
experimental deprecated command getCookies
......@@ -5705,7 +5709,6 @@ domain Page
# Base64-encoded data
binary data
domain Performance
# Run-time execution metric.
......
......@@ -74,7 +74,7 @@
{
"domain": "Page",
"exclude": ["getNavigationHistory", "navigateToHistoryEntry", "resetNavigationHistory", "captureScreenshot", "screencastFrameAck", "handleJavaScriptDialog", "setColorPickerEnabled",
"getAppManifest", "setControlNavigations", "processNavigation", "printToPDF", "bringToFront", "setDownloadBehavior", "navigate", "crash", "close", "setWebLifecycleState", "captureSnapshot"],
"getAppManifest", "setControlNavigations", "processNavigation", "printToPDF", "bringToFront", "setDownloadBehavior", "navigate", "crash", "close", "setWebLifecycleState", "captureSnapshot", "getInstallabilityErrors"],
"async": ["getResourceContent", "searchInResource"],
"exclude_events": ["screencastFrame", "screencastVisibilityChanged", "colorPicked", "interstitialShown", "interstitialHidden", "javascriptDialogOpening", "javascriptDialogClosed", "navigationRequested"]
},
......
......@@ -93,15 +93,17 @@ Resources.AppManifestView = class extends UI.VBox {
*/
async _updateManifest(immediately) {
const {url, data, errors} = await this._resourceTreeModel.fetchAppManifest();
this._throttler.schedule(() => this._renderManifest(url, data, errors), immediately);
const installabilityErrors = await this._resourceTreeModel.getInstallabilityErrors();
this._throttler.schedule(() => this._renderManifest(url, data, errors, installabilityErrors), immediately);
}
/**
* @param {string} url
* @param {?string} data
* @param {!Array<!Protocol.Page.AppManifestError>} errors
* @param {!Array<string>} installabilityErrors
*/
async _renderManifest(url, data, errors) {
async _renderManifest(url, data, errors, installabilityErrors) {
if (!data && !errors.length) {
this._emptyView.showWidget();
this._reportView.hideWidget();
......@@ -121,31 +123,20 @@ Resources.AppManifestView = class extends UI.VBox {
if (!data)
return;
const installabilityErrors = [];
if (data.charCodeAt(0) === 0xFEFF)
data = data.slice(1); // Trim the BOM as per https://tools.ietf.org/html/rfc7159#section-8.1.
const parsedManifest = JSON.parse(data);
this._nameField.textContent = stringProperty('name');
this._shortNameField.textContent = stringProperty('short_name');
if (!this._nameField.textContent && !this._shortNameField.textContent)
installabilityErrors.push(ls`Either 'name' or 'short_name' is required`);
this._startURLField.removeChildren();
const startURL = stringProperty('start_url');
if (startURL) {
const completeURL = /** @type {string} */ (Common.ParsedURL.completeURL(url, startURL));
this._startURLField.appendChild(Components.Linkifier.linkifyURL(completeURL, {text: startURL}));
if (!this._serviceWorkerManager.hasRegistrationForURLs([completeURL, this._target.inspectedURL()]))
installabilityErrors.push(ls`Service worker is not registered or does not control the Start URL`);
else if (!await this._swHasFetchHandler())
installabilityErrors.push(ls`Service worker does not have the 'fetch' handler`);
} else {
installabilityErrors.push(ls`'start_url' needs to be a valid URL`);
}
this._themeColorSwatch.classList.toggle('hidden', !stringProperty('theme_color'));
const themeColor = Common.Color.parse(stringProperty('theme_color') || 'white') || Common.Color.parse('white');
this._themeColorSwatch.setColor(/** @type {!Common.Color} */ (themeColor));
......@@ -157,33 +148,17 @@ Resources.AppManifestView = class extends UI.VBox {
this._orientationField.textContent = stringProperty('orientation');
const displayType = stringProperty('display');
this._displayField.textContent = displayType;
if (!['minimal-ui', 'standalone', 'fullscreen'].includes(displayType))
installabilityErrors.push(ls`'display' property must be set to 'standalone', 'fullscreen' or 'minimal-ui'`);
const icons = parsedManifest['icons'] || [];
let hasInstallableIcon = false;
this._iconsSection.clearContent();
for (const icon of icons) {
if (!icon.sizes)
hasInstallableIcon = true; // any
const title = (icon['sizes'] || '') + '\n' + (icon['type'] || '');
try {
const widthHeight = icon['sizes'].split('x');
if (parseInt(widthHeight[0], 10) >= 144 && parseInt(widthHeight[1], 10) >= 144)
hasInstallableIcon = true;
} catch (e) {
}
const field = this._iconsSection.appendField(title);
const image = await this._loadImage(Common.ParsedURL.completeURL(url, icon['src']));
if (image)
field.appendChild(image);
else
installabilityErrors.push(ls`Some of the icons could not be loaded`);
}
if (!hasInstallableIcon)
installabilityErrors.push(ls`An icon at least 144px x 144px large is required`);
this._installabilitySection.clearContent();
this._installabilitySection.element.classList.toggle('hidden', !installabilityErrors.length);
......@@ -202,32 +177,6 @@ Resources.AppManifestView = class extends UI.VBox {
}
}
/**
* @return {!Promise<boolean>}
*/
async _swHasFetchHandler() {
for (const target of SDK.targetManager.targets()) {
if (target.type() !== SDK.Target.Type.Worker)
continue;
if (!target.parentTarget() || target.parentTarget().type() !== SDK.Target.Type.ServiceWorker)
continue;
const ec = target.model(SDK.RuntimeModel).defaultExecutionContext();
const result = await ec.evaluate(
{
expression: `'fetch' in getEventListeners(self)`,
includeCommandLineAPI: true,
silent: true,
returnByValue: true
},
false, false);
if (!result.object || !result.object.value)
continue;
return true;
}
return false;
}
/**
* @param {?string} url
* @return {!Promise<?Image>}
......
......@@ -411,6 +411,15 @@ SDK.ResourceTreeModel = class extends SDK.SDKModel {
return {url: response.url, data: null, errors: []};
return {url: response.url, data: response.data || null, errors: response.errors};
}
/**
* @return {!Promise<!Array<string>>}
*/
async getInstallabilityErrors() {
const response = await this._agent.invoke_getInstallabilityErrors({});
return response.errors || [];
}
/**
* @param {!SDK.ExecutionContext} a
* @param {!SDK.ExecutionContext} b
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment