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 @@ ...@@ -8,8 +8,9 @@
"options": [ "options": [
{ {
"domain": "Page", "domain": "Page",
"include": [ "enable", "disable", "setAdBlockingEnabled" ], "include": [ "enable", "disable", "setAdBlockingEnabled", "getInstallabilityErrors" ],
"include_events": [] "include_events": [],
"async": ["getInstallabilityErrors"]
}, },
{ {
"domain": "Browser", "domain": "Browser",
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "chrome/browser/devtools/protocol/page_handler.h" #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" #include "chrome/browser/subresource_filter/chrome_subresource_filter_client.h"
PageHandler::PageHandler(content::WebContents* web_contents, PageHandler::PageHandler(content::WebContents* web_contents,
...@@ -47,3 +48,28 @@ protocol::Response PageHandler::SetAdBlockingEnabled(bool enabled) { ...@@ -47,3 +48,28 @@ protocol::Response PageHandler::SetAdBlockingEnabled(bool enabled) {
ToggleAdBlocking(enabled); ToggleAdBlocking(enabled);
return protocol::Response::OK(); 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, ...@@ -26,8 +26,14 @@ class PageHandler : public protocol::Page::Backend,
protocol::Response Enable() override; protocol::Response Enable() override;
protocol::Response Disable() override; protocol::Response Disable() override;
protocol::Response SetAdBlockingEnabled(bool enabled) override; protocol::Response SetAdBlockingEnabled(bool enabled) override;
void GetInstallabilityErrors(
std::unique_ptr<GetInstallabilityErrorsCallback> callback) override;
private: private:
static void GotInstallabilityErrors(
std::unique_ptr<GetInstallabilityErrorsCallback> callback,
std::vector<std::string> errors);
bool enabled_ = false; bool enabled_ = false;
DISALLOW_COPY_AND_ASSIGN(PageHandler); DISALLOW_COPY_AND_ASSIGN(PageHandler);
......
...@@ -1155,5 +1155,13 @@ Response PageHandler::SetWebLifecycleState(const std::string& state) { ...@@ -1155,5 +1155,13 @@ Response PageHandler::SetWebLifecycleState(const std::string& state) {
return Response::Error("Unidentified lifecycle 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 protocol
} // namespace content } // namespace content
...@@ -155,6 +155,8 @@ class PageHandler : public DevToolsDomainHandler, ...@@ -155,6 +155,8 @@ class PageHandler : public DevToolsDomainHandler,
std::unique_ptr<GetAppManifestCallback> callback) override; std::unique_ptr<GetAppManifestCallback> callback) override;
Response SetWebLifecycleState(const std::string& state) override; Response SetWebLifecycleState(const std::string& state) override;
void GetInstallabilityErrors(
std::unique_ptr<GetInstallabilityErrorsCallback> callback) override;
private: private:
enum EncodingFormat { PNG, JPEG }; enum EncodingFormat { PNG, JPEG };
......
...@@ -57,9 +57,9 @@ ...@@ -57,9 +57,9 @@
"domain": "Page", "domain": "Page",
"include": ["enable", "disable", "reload", "navigate", "stopLoading", "getNavigationHistory", "navigateToHistoryEntry", "resetNavigationHistory", "captureScreenshot", "include": ["enable", "disable", "reload", "navigate", "stopLoading", "getNavigationHistory", "navigateToHistoryEntry", "resetNavigationHistory", "captureScreenshot",
"startScreencast", "stopScreencast", "screencastFrameAck", "handleJavaScriptDialog", "setColorPickerEnabled", "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"], "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", "domain": "Runtime",
......
...@@ -5173,6 +5173,10 @@ domain Page ...@@ -5173,6 +5173,10 @@ domain Page
# Manifest content. # Manifest content.
optional string data optional string data
experimental command getInstallabilityErrors
returns
array of string errors
# Returns all browser cookies. Depending on the backend support, will return detailed cookie # Returns all browser cookies. Depending on the backend support, will return detailed cookie
# information in the `cookies` field. # information in the `cookies` field.
experimental deprecated command getCookies experimental deprecated command getCookies
...@@ -5705,7 +5709,6 @@ domain Page ...@@ -5705,7 +5709,6 @@ domain Page
# Base64-encoded data # Base64-encoded data
binary data binary data
domain Performance domain Performance
# Run-time execution metric. # Run-time execution metric.
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
{ {
"domain": "Page", "domain": "Page",
"exclude": ["getNavigationHistory", "navigateToHistoryEntry", "resetNavigationHistory", "captureScreenshot", "screencastFrameAck", "handleJavaScriptDialog", "setColorPickerEnabled", "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"], "async": ["getResourceContent", "searchInResource"],
"exclude_events": ["screencastFrame", "screencastVisibilityChanged", "colorPicked", "interstitialShown", "interstitialHidden", "javascriptDialogOpening", "javascriptDialogClosed", "navigationRequested"] "exclude_events": ["screencastFrame", "screencastVisibilityChanged", "colorPicked", "interstitialShown", "interstitialHidden", "javascriptDialogOpening", "javascriptDialogClosed", "navigationRequested"]
}, },
......
...@@ -93,15 +93,17 @@ Resources.AppManifestView = class extends UI.VBox { ...@@ -93,15 +93,17 @@ Resources.AppManifestView = class extends UI.VBox {
*/ */
async _updateManifest(immediately) { async _updateManifest(immediately) {
const {url, data, errors} = await this._resourceTreeModel.fetchAppManifest(); 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} url
* @param {?string} data * @param {?string} data
* @param {!Array<!Protocol.Page.AppManifestError>} errors * @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) { if (!data && !errors.length) {
this._emptyView.showWidget(); this._emptyView.showWidget();
this._reportView.hideWidget(); this._reportView.hideWidget();
...@@ -121,31 +123,20 @@ Resources.AppManifestView = class extends UI.VBox { ...@@ -121,31 +123,20 @@ Resources.AppManifestView = class extends UI.VBox {
if (!data) if (!data)
return; return;
const installabilityErrors = [];
if (data.charCodeAt(0) === 0xFEFF) if (data.charCodeAt(0) === 0xFEFF)
data = data.slice(1); // Trim the BOM as per https://tools.ietf.org/html/rfc7159#section-8.1. data = data.slice(1); // Trim the BOM as per https://tools.ietf.org/html/rfc7159#section-8.1.
const parsedManifest = JSON.parse(data); const parsedManifest = JSON.parse(data);
this._nameField.textContent = stringProperty('name'); this._nameField.textContent = stringProperty('name');
this._shortNameField.textContent = stringProperty('short_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(); this._startURLField.removeChildren();
const startURL = stringProperty('start_url'); const startURL = stringProperty('start_url');
if (startURL) { if (startURL) {
const completeURL = /** @type {string} */ (Common.ParsedURL.completeURL(url, startURL)); const completeURL = /** @type {string} */ (Common.ParsedURL.completeURL(url, startURL));
this._startURLField.appendChild(Components.Linkifier.linkifyURL(completeURL, {text: 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')); this._themeColorSwatch.classList.toggle('hidden', !stringProperty('theme_color'));
const themeColor = Common.Color.parse(stringProperty('theme_color') || 'white') || Common.Color.parse('white'); const themeColor = Common.Color.parse(stringProperty('theme_color') || 'white') || Common.Color.parse('white');
this._themeColorSwatch.setColor(/** @type {!Common.Color} */ (themeColor)); this._themeColorSwatch.setColor(/** @type {!Common.Color} */ (themeColor));
...@@ -157,33 +148,17 @@ Resources.AppManifestView = class extends UI.VBox { ...@@ -157,33 +148,17 @@ Resources.AppManifestView = class extends UI.VBox {
this._orientationField.textContent = stringProperty('orientation'); this._orientationField.textContent = stringProperty('orientation');
const displayType = stringProperty('display'); const displayType = stringProperty('display');
this._displayField.textContent = displayType; 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'] || []; const icons = parsedManifest['icons'] || [];
let hasInstallableIcon = false;
this._iconsSection.clearContent(); this._iconsSection.clearContent();
for (const icon of icons) { for (const icon of icons) {
if (!icon.sizes)
hasInstallableIcon = true; // any
const title = (icon['sizes'] || '') + '\n' + (icon['type'] || ''); 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 field = this._iconsSection.appendField(title);
const image = await this._loadImage(Common.ParsedURL.completeURL(url, icon['src'])); const image = await this._loadImage(Common.ParsedURL.completeURL(url, icon['src']));
if (image) if (image)
field.appendChild(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.clearContent();
this._installabilitySection.element.classList.toggle('hidden', !installabilityErrors.length); this._installabilitySection.element.classList.toggle('hidden', !installabilityErrors.length);
...@@ -202,32 +177,6 @@ Resources.AppManifestView = class extends UI.VBox { ...@@ -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 * @param {?string} url
* @return {!Promise<?Image>} * @return {!Promise<?Image>}
......
...@@ -411,6 +411,15 @@ SDK.ResourceTreeModel = class extends SDK.SDKModel { ...@@ -411,6 +411,15 @@ SDK.ResourceTreeModel = class extends SDK.SDKModel {
return {url: response.url, data: null, errors: []}; return {url: response.url, data: null, errors: []};
return {url: response.url, data: response.data || null, errors: response.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} a
* @param {!SDK.ExecutionContext} b * @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