mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-07 12:33:47 +08:00
Inline stylesheets are not correctly linked in styles pane if document source url is different from root domain url for multi-page applications. Before: https://i.imgur.com/M0t7WTy.png After: https://i.imgur.com/WsOdosd.png Bug:1133930 Change-Id: I347be9b0e80235a5929f688092fcac5c3ea8cf94 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2441882 Commit-Queue: Chait Pinnamaneni <cpinnamaneni@microsoft.com> Reviewed-by: Michael Liao <michael.liao@microsoft.com> Reviewed-by: Shane Clifford <shanejc@microsoft.com> Reviewed-by: Robert Paveza <Rob.Paveza@microsoft.com>
411 lines
12 KiB
JavaScript
411 lines
12 KiB
JavaScript
// Copyright 2014 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.
|
|
|
|
import * as Common from '../common/common.js';
|
|
import * as Platform from '../platform/platform.js';
|
|
import * as SDK from '../sdk/sdk.js';
|
|
import * as Workspace from '../workspace/workspace.js'; // eslint-disable-line no-unused-vars
|
|
|
|
import {LiveLocation as LiveLocationInterface, LiveLocationPool, LiveLocationWithPool,} from './LiveLocation.js'; // eslint-disable-line no-unused-vars
|
|
import {ResourceMapping} from './ResourceMapping.js';
|
|
import {SASSSourceMapping} from './SASSSourceMapping.js';
|
|
import {StylesSourceMapping} from './StylesSourceMapping.js';
|
|
|
|
/**
|
|
* @type {!CSSWorkspaceBinding}
|
|
*/
|
|
let cssWorkspaceBindingInstance;
|
|
|
|
/**
|
|
* @implements {SDK.SDKModel.SDKModelObserver<!SDK.CSSModel.CSSModel>}
|
|
*/
|
|
export class CSSWorkspaceBinding {
|
|
/**
|
|
* @private
|
|
* @param {!SDK.SDKModel.TargetManager} targetManager
|
|
* @param {!Workspace.Workspace.WorkspaceImpl} workspace
|
|
*/
|
|
constructor(targetManager, workspace) {
|
|
this._workspace = workspace;
|
|
|
|
/** @type {!Map.<!SDK.CSSModel.CSSModel, !ModelInfo>} */
|
|
this._modelToInfo = new Map();
|
|
/** @type {!Array<!SourceMapping>} */
|
|
this._sourceMappings = [];
|
|
targetManager.observeModels(SDK.CSSModel.CSSModel, this);
|
|
|
|
/** @type {!Set.<!Promise<?>>} */
|
|
this._liveLocationPromises = new Set();
|
|
}
|
|
|
|
/**
|
|
* @param {{forceNew: ?boolean, targetManager: ?SDK.SDKModel.TargetManager, workspace: ?Workspace.Workspace.WorkspaceImpl}} opts
|
|
*/
|
|
static instance(opts = {forceNew: null, targetManager: null, workspace: null}) {
|
|
const {forceNew, targetManager, workspace} = opts;
|
|
if (!cssWorkspaceBindingInstance || forceNew) {
|
|
if (!targetManager || !workspace) {
|
|
throw new Error(
|
|
`Unable to create settings: targetManager and workspace must be provided: ${new Error().stack}`);
|
|
}
|
|
|
|
cssWorkspaceBindingInstance = new CSSWorkspaceBinding(targetManager, workspace);
|
|
}
|
|
|
|
return cssWorkspaceBindingInstance;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CSSModel.CSSModel} cssModel
|
|
* @return {!ModelInfo}
|
|
*/
|
|
_getCSSModelInfo(cssModel) {
|
|
return /** @type {!ModelInfo} */ (this._modelToInfo.get(cssModel));
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!SDK.CSSModel.CSSModel} cssModel
|
|
*/
|
|
modelAdded(cssModel) {
|
|
this._modelToInfo.set(cssModel, new ModelInfo(cssModel, this._workspace));
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!SDK.CSSModel.CSSModel} cssModel
|
|
*/
|
|
modelRemoved(cssModel) {
|
|
this._getCSSModelInfo(cssModel)._dispose();
|
|
this._modelToInfo.delete(cssModel);
|
|
}
|
|
|
|
/**
|
|
* The promise returned by this function is resolved once all *currently*
|
|
* pending LiveLocations are processed.
|
|
*
|
|
* @return {!Promise<?>}
|
|
*/
|
|
async pendingLiveLocationChangesPromise() {
|
|
await Promise.all(this._liveLocationPromises);
|
|
}
|
|
|
|
/**
|
|
* @param {!Promise<?>} promise
|
|
*/
|
|
_recordLiveLocationChange(promise) {
|
|
promise.then(() => {
|
|
this._liveLocationPromises.delete(promise);
|
|
});
|
|
this._liveLocationPromises.add(promise);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CSSStyleSheetHeader.CSSStyleSheetHeader} header
|
|
* @return {!Promise<void>}
|
|
*/
|
|
async updateLocations(header) {
|
|
const updatePromise = this._getCSSModelInfo(header.cssModel())._updateLocations(header);
|
|
this._recordLiveLocationChange(updatePromise);
|
|
await updatePromise;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CSSModel.CSSLocation} rawLocation
|
|
* @param {function(!LiveLocationInterface):!Promise<void>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
* @return {!Promise<!LiveLocation>}
|
|
*/
|
|
createLiveLocation(rawLocation, updateDelegate, locationPool) {
|
|
const locationPromise =
|
|
this._getCSSModelInfo(rawLocation.cssModel())._createLiveLocation(rawLocation, updateDelegate, locationPool);
|
|
this._recordLiveLocationChange(locationPromise);
|
|
return locationPromise;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CSSProperty.CSSProperty} cssProperty
|
|
* @param {boolean} forName
|
|
* @return {?Workspace.UISourceCode.UILocation}
|
|
*/
|
|
propertyUILocation(cssProperty, forName) {
|
|
const style = cssProperty.ownerStyle;
|
|
if (!style || style.type !== SDK.CSSStyleDeclaration.Type.Regular || !style.styleSheetId) {
|
|
return null;
|
|
}
|
|
const header = style.cssModel().styleSheetHeaderForId(style.styleSheetId);
|
|
if (!header) {
|
|
return null;
|
|
}
|
|
|
|
const range = forName ? cssProperty.nameRange() : cssProperty.valueRange();
|
|
if (!range) {
|
|
return null;
|
|
}
|
|
|
|
const lineNumber = range.startLine;
|
|
const columnNumber = range.startColumn;
|
|
const rawLocation = new SDK.CSSModel.CSSLocation(
|
|
header, header.lineNumberInSource(lineNumber), header.columnNumberInSource(lineNumber, columnNumber));
|
|
return this.rawLocationToUILocation(rawLocation);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CSSModel.CSSLocation} rawLocation
|
|
* @return {?Workspace.UISourceCode.UILocation}
|
|
*/
|
|
rawLocationToUILocation(rawLocation) {
|
|
for (let i = this._sourceMappings.length - 1; i >= 0; --i) {
|
|
const uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation);
|
|
if (uiLocation) {
|
|
return uiLocation;
|
|
}
|
|
}
|
|
return this._getCSSModelInfo(rawLocation.cssModel())._rawLocationToUILocation(rawLocation);
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UILocation} uiLocation
|
|
* @return {!Array<!SDK.CSSModel.CSSLocation>}
|
|
*/
|
|
uiLocationToRawLocations(uiLocation) {
|
|
for (let i = this._sourceMappings.length - 1; i >= 0; --i) {
|
|
const rawLocations = this._sourceMappings[i].uiLocationToRawLocations(uiLocation);
|
|
if (rawLocations.length) {
|
|
return rawLocations;
|
|
}
|
|
}
|
|
const rawLocations = [];
|
|
for (const modelInfo of this._modelToInfo.values()) {
|
|
rawLocations.push(...modelInfo._uiLocationToRawLocations(uiLocation));
|
|
}
|
|
return rawLocations;
|
|
}
|
|
|
|
/**
|
|
* @param {!SourceMapping} sourceMapping
|
|
*/
|
|
addSourceMapping(sourceMapping) {
|
|
this._sourceMappings.push(sourceMapping);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @interface
|
|
*/
|
|
export class SourceMapping {
|
|
/**
|
|
* @param {!SDK.CSSModel.CSSLocation} rawLocation
|
|
* @return {?Workspace.UISourceCode.UILocation}
|
|
*/
|
|
rawLocationToUILocation(rawLocation) {
|
|
throw new Error('Not implemented yet');
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UILocation} uiLocation
|
|
* @return {!Array<!SDK.CSSModel.CSSLocation>}
|
|
*/
|
|
uiLocationToRawLocations(uiLocation) {
|
|
throw new Error('Not implemented yet');
|
|
}
|
|
}
|
|
|
|
export class ModelInfo {
|
|
/**
|
|
* @param {!SDK.CSSModel.CSSModel} cssModel
|
|
* @param {!Workspace.Workspace.WorkspaceImpl} workspace
|
|
*/
|
|
constructor(cssModel, workspace) {
|
|
this._eventListeners = [
|
|
cssModel.addEventListener(
|
|
SDK.CSSModel.Events.StyleSheetAdded,
|
|
event => {
|
|
this._styleSheetAdded(event);
|
|
},
|
|
this),
|
|
cssModel.addEventListener(
|
|
SDK.CSSModel.Events.StyleSheetRemoved,
|
|
event => {
|
|
this._styleSheetRemoved(event);
|
|
},
|
|
this)
|
|
];
|
|
|
|
this._stylesSourceMapping = new StylesSourceMapping(cssModel, workspace);
|
|
const sourceMapManager = cssModel.sourceMapManager();
|
|
this._sassSourceMapping = new SASSSourceMapping(cssModel.target(), sourceMapManager, workspace);
|
|
|
|
/** @type {!Platform.Multimap<!SDK.CSSStyleSheetHeader.CSSStyleSheetHeader, !LiveLocation>} */
|
|
this._locations = new Platform.Multimap();
|
|
/** @type {!Platform.Multimap<string, !LiveLocation>} */
|
|
this._unboundLocations = new Platform.Multimap();
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CSSModel.CSSLocation} rawLocation
|
|
* @param {function(!LiveLocationInterface):!Promise<void>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
* @return {!Promise<!LiveLocation>}
|
|
*/
|
|
async _createLiveLocation(rawLocation, updateDelegate, locationPool) {
|
|
const location = new LiveLocation(rawLocation, this, updateDelegate, locationPool);
|
|
const header = rawLocation.header();
|
|
if (header) {
|
|
location._header = header;
|
|
this._locations.set(header, location);
|
|
await location.update();
|
|
} else {
|
|
this._unboundLocations.set(rawLocation.url, location);
|
|
}
|
|
return location;
|
|
}
|
|
|
|
/**
|
|
* @param {!LiveLocation} location
|
|
*/
|
|
_disposeLocation(location) {
|
|
if (location._header) {
|
|
this._locations.delete(location._header, location);
|
|
} else {
|
|
this._unboundLocations.delete(location._url, location);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CSSStyleSheetHeader.CSSStyleSheetHeader} header
|
|
*/
|
|
_updateLocations(header) {
|
|
const promises = [];
|
|
for (const location of this._locations.get(header)) {
|
|
promises.push(location.update());
|
|
}
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.EventTarget.EventTargetEvent} event
|
|
*/
|
|
async _styleSheetAdded(event) {
|
|
const header = /** @type {!SDK.CSSStyleSheetHeader.CSSStyleSheetHeader} */ (event.data);
|
|
if (!header.sourceURL) {
|
|
return;
|
|
}
|
|
|
|
const promises = [];
|
|
for (const location of this._unboundLocations.get(header.sourceURL)) {
|
|
location._header = header;
|
|
this._locations.set(header, location);
|
|
promises.push(location.update());
|
|
}
|
|
await Promise.all(promises);
|
|
this._unboundLocations.deleteAll(header.sourceURL);
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.EventTarget.EventTargetEvent} event
|
|
*/
|
|
async _styleSheetRemoved(event) {
|
|
const header = /** @type {!SDK.CSSStyleSheetHeader.CSSStyleSheetHeader} */ (event.data);
|
|
const promises = [];
|
|
for (const location of this._locations.get(header)) {
|
|
location._header = null;
|
|
this._unboundLocations.set(location._url, location);
|
|
promises.push(location.update());
|
|
}
|
|
await Promise.all(promises);
|
|
this._locations.deleteAll(header);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.CSSModel.CSSLocation} rawLocation
|
|
* @return {?Workspace.UISourceCode.UILocation}
|
|
*/
|
|
_rawLocationToUILocation(rawLocation) {
|
|
let uiLocation = null;
|
|
uiLocation = uiLocation || this._sassSourceMapping.rawLocationToUILocation(rawLocation);
|
|
uiLocation = uiLocation || this._stylesSourceMapping.rawLocationToUILocation(rawLocation);
|
|
uiLocation = uiLocation || ResourceMapping.instance().cssLocationToUILocation(rawLocation);
|
|
return uiLocation;
|
|
}
|
|
|
|
/**
|
|
* @param {!Workspace.UISourceCode.UILocation} uiLocation
|
|
* @return {!Array<!SDK.CSSModel.CSSLocation>}
|
|
*/
|
|
_uiLocationToRawLocations(uiLocation) {
|
|
let rawLocations = this._sassSourceMapping.uiLocationToRawLocations(uiLocation);
|
|
if (rawLocations.length) {
|
|
return rawLocations;
|
|
}
|
|
rawLocations = this._stylesSourceMapping.uiLocationToRawLocations(uiLocation);
|
|
if (rawLocations.length) {
|
|
return rawLocations;
|
|
}
|
|
return ResourceMapping.instance().uiLocationToCSSLocations(uiLocation);
|
|
}
|
|
|
|
_dispose() {
|
|
Common.EventTarget.EventTarget.removeEventListeners(this._eventListeners);
|
|
this._stylesSourceMapping.dispose();
|
|
this._sassSourceMapping.dispose();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @unrestricted
|
|
*/
|
|
export class LiveLocation extends LiveLocationWithPool {
|
|
/**
|
|
* @param {!SDK.CSSModel.CSSLocation} rawLocation
|
|
* @param {!ModelInfo} info
|
|
* @param {function(!LiveLocationInterface):!Promise<void>} updateDelegate
|
|
* @param {!LiveLocationPool} locationPool
|
|
*/
|
|
constructor(rawLocation, info, updateDelegate, locationPool) {
|
|
super(updateDelegate, locationPool);
|
|
this._url = rawLocation.url;
|
|
this._lineNumber = rawLocation.lineNumber;
|
|
this._columnNumber = rawLocation.columnNumber;
|
|
this._info = info;
|
|
/** @type {?SDK.CSSStyleSheetHeader.CSSStyleSheetHeader} */
|
|
this._header = null;
|
|
}
|
|
|
|
/**
|
|
* @return {?SDK.CSSStyleSheetHeader.CSSStyleSheetHeader}
|
|
*/
|
|
header() {
|
|
return this._header;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {!Promise<?Workspace.UISourceCode.UILocation>}
|
|
*/
|
|
async uiLocation() {
|
|
if (!this._header) {
|
|
return null;
|
|
}
|
|
const rawLocation = new SDK.CSSModel.CSSLocation(this._header, this._lineNumber, this._columnNumber);
|
|
return CSSWorkspaceBinding.instance().rawLocationToUILocation(rawLocation);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
dispose() {
|
|
super.dispose();
|
|
this._info._disposeLocation(this);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
async isBlackboxed() {
|
|
return false;
|
|
}
|
|
}
|